diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2')
68 files changed, 0 insertions, 32214 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java deleted file mode 100644 index b3dce0756..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java +++ /dev/null @@ -1,396 +0,0 @@ -/* - * 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.jface.resource.JFaceResources; -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.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 org.eclipse.swt.widgets.ScrollBar; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * 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, Object header, int style) { - Composite composite = new Composite(parent, style); - 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 - * @param expand Set of headers to expand initially - */ - public AccordionControl(Composite parent, int style, List<?> headers, - boolean greedy, boolean wrapChildren, Set<String> expand) { - 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$ - List<CLabel> expandLabels = new ArrayList<CLabel>(); - - 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 = JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT); - } - 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() { - @Override - public void mouseEnter(MouseEvent e) { - updateBackground(label, true); - } - - @Override - public void mouseExit(MouseEvent e) { - updateBackground(label, false); - } - - @Override - public void mouseHover(MouseEvent e) { - } - }); - - // Turn off border? - final ScrolledComposite scrolledComposite = new ScrolledComposite(this, SWT.V_SCROLL); - ScrollBar verticalBar = scrolledComposite.getVerticalBar(); - verticalBar.setIncrement(20); - verticalBar.setPageIncrement(100); - - // 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); - ScrollBar vBar = scrolledComposite.getVerticalBar(); - vBar.setPageIncrement(r.height); - } - } - }); - } - - updateIcon(label); - if (expand != null && expand.contains(label.getText())) { - // Comparing "label.getText()" rather than "header" because we make some - // tweaks to the label (replacing & with && etc) and in the getExpandedCategories - // method we return the label texts - expandLabels.add(label); - } - } - - // Expand the requested categories - for (CLabel label : expandLabels) { - toggle(label, 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) { - Object header = getHeader(label); - Composite composite = createChildContainer(scrolledComposite, header, SWT.NONE); - createChildren(composite, header); - while (composite.getParent() != scrolledComposite) { - composite = composite.getParent(); - } - 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 - } - - /** - * Returns the set of expanded categories in the palette. Note: Header labels will have - * escaped ampersand characters with double ampersands. - * - * @return the set of expanded categories in the palette - never null - */ - public Set<String> getExpandedCategories() { - Set<String> expanded = new HashSet<String>(); - for (Control c : getChildren()) { - if (c instanceof CLabel) { - if (isOpen(c)) { - expanded.add(((CLabel) c).getText()); - } - } - } - - return expanded; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/BinPacker.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/BinPacker.java deleted file mode 100644 index 9fc2e0937..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/BinPacker.java +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.Nullable; -import com.android.ide.common.api.Rect; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import javax.imageio.ImageIO; - -/** - * This class implements 2D bin packing: packing rectangles into a given area as - * tightly as "possible" (bin packing in general is NP hard, so this class uses - * heuristics). - * <p> - * The algorithm implemented is to keep a set of (possibly overlapping) - * available areas for placement. For each newly inserted rectangle, we first - * pick which available space to occupy, and we then subdivide the - * current rectangle into all the possible remaining unoccupied sub-rectangles. - * We also remove any other space rectangles which are no longer eligible if - * they are intersecting the newly placed rectangle. - * <p> - * This algorithm is not very fast, so should not be used for a large number of - * rectangles. - */ -class BinPacker { - /** - * When enabled, the successive passes are dumped as PNG images showing the - * various available and occupied rectangles) - */ - private static final boolean DEBUG = false; - - private final List<Rect> mSpace = new ArrayList<Rect>(); - private final int mMinHeight; - private final int mMinWidth; - - /** - * Creates a new {@linkplain BinPacker}. To use it, first add one or more - * initial available space rectangles with {@link #addSpace(Rect)}, and then - * place the rectangles with {@link #occupy(int, int)}. The returned - * {@link Rect} from {@link #occupy(int, int)} gives the coordinates of the - * positioned rectangle. - * - * @param minWidth the smallest width of any rectangle placed into this bin - * @param minHeight the smallest height of any rectangle placed into this bin - */ - BinPacker(int minWidth, int minHeight) { - mMinWidth = minWidth; - mMinHeight = minHeight; - - if (DEBUG) { - mAllocated = new ArrayList<Rect>(); - sLayoutId++; - sRectId = 1; - } - } - - /** Adds more available space */ - void addSpace(Rect rect) { - if (rect.w >= mMinWidth && rect.h >= mMinHeight) { - mSpace.add(rect); - } - } - - /** Attempts to place a rectangle of the given dimensions, if possible */ - @Nullable - Rect occupy(int width, int height) { - int index = findBest(width, height); - if (index == -1) { - return null; - } - - return split(index, width, height); - } - - /** - * Finds the best available space rectangle to position a new rectangle of - * the given size in. - */ - private int findBest(int width, int height) { - if (mSpace.isEmpty()) { - return -1; - } - - // Try to pack as far up as possible first - int bestIndex = -1; - boolean multipleAtSameY = false; - int minY = Integer.MAX_VALUE; - for (int i = 0, n = mSpace.size(); i < n; i++) { - Rect rect = mSpace.get(i); - if (rect.y <= minY) { - if (rect.w >= width && rect.h >= height) { - if (rect.y < minY) { - minY = rect.y; - multipleAtSameY = false; - bestIndex = i; - } else if (minY == rect.y) { - multipleAtSameY = true; - } - } - } - } - - if (!multipleAtSameY) { - return bestIndex; - } - - bestIndex = -1; - - // Pick a rectangle. This currently tries to find the rectangle whose shortest - // side most closely matches the placed rectangle's size. - // Attempt to find the best short side fit - int bestShortDistance = Integer.MAX_VALUE; - int bestLongDistance = Integer.MAX_VALUE; - - for (int i = 0, n = mSpace.size(); i < n; i++) { - Rect rect = mSpace.get(i); - if (rect.y != minY) { // Only comparing elements at same y - continue; - } - if (rect.w >= width && rect.h >= height) { - if (width < height) { - int distance = rect.w - width; - if (distance < bestShortDistance || - distance == bestShortDistance && - (rect.h - height) < bestLongDistance) { - bestShortDistance = distance; - bestLongDistance = rect.h - height; - bestIndex = i; - } - } else { - int distance = rect.w - width; - if (distance < bestShortDistance || - distance == bestShortDistance && - (rect.h - height) < bestLongDistance) { - bestShortDistance = distance; - bestLongDistance = rect.h - height; - bestIndex = i; - } - } - } - } - - return bestIndex; - } - - /** - * Removes the rectangle at the given index. Since the rectangles are in an - * {@link ArrayList}, removing a rectangle in the normal way is slow (it - * would involve shifting all elements), but since we don't care about - * order, this always swaps the to-be-deleted element to the last position - * in the array first, <b>then</b> it deletes it (which should be - * immediate). - * - * @param index the index in the {@link #mSpace} list to remove a rectangle - * from - */ - private void removeRect(int index) { - assert !mSpace.isEmpty(); - int lastIndex = mSpace.size() - 1; - if (index != lastIndex) { - // Swap before remove to make deletion faster since we don't - // care about order - Rect temp = mSpace.get(index); - mSpace.set(index, mSpace.get(lastIndex)); - mSpace.set(lastIndex, temp); - } - - mSpace.remove(lastIndex); - } - - /** - * Splits the rectangle at the given rectangle index such that it can contain - * a rectangle of the given width and height. */ - private Rect split(int index, int width, int height) { - Rect rect = mSpace.get(index); - assert rect.w >= width && rect.h >= height : rect; - - Rect r = new Rect(rect); - r.w = width; - r.h = height; - - // Remove all rectangles that intersect my rectangle - for (int i = 0; i < mSpace.size(); i++) { - Rect other = mSpace.get(i); - if (other.intersects(r)) { - removeRect(i); - i--; - } - } - - - // Split along vertical line x = rect.x + width: - // (rect.x,rect.y) - // +-------------+-------------------------+ - // | | | - // | | | - // | | height | - // | | | - // | | | - // +-------------+ B | rect.h - // | width | - // | | | - // | A | - // | | | - // | | - // +---------------------------------------+ - // rect.w - int remainingHeight = rect.h - height; - int remainingWidth = rect.w - width; - if (remainingHeight >= mMinHeight) { - mSpace.add(new Rect(rect.x, rect.y + height, width, remainingHeight)); - } - if (remainingWidth >= mMinWidth) { - mSpace.add(new Rect(rect.x + width, rect.y, remainingWidth, rect.h)); - } - - // Split along horizontal line y = rect.y + height: - // +-------------+-------------------------+ - // | | | - // | | height | - // | | A | - // | | | - // | | | rect.h - // +-------------+ - - - - - - - - - - - - | - // | width | - // | | - // | B | - // | | - // | | - // +---------------------------------------+ - // rect.w - if (remainingHeight >= mMinHeight) { - mSpace.add(new Rect(rect.x, rect.y + height, rect.w, remainingHeight)); - } - if (remainingWidth >= mMinWidth) { - mSpace.add(new Rect(rect.x + width, rect.y, remainingWidth, height)); - } - - // Remove redundant rectangles. This is not very efficient. - for (int i = 0; i < mSpace.size() - 1; i++) { - for (int j = i + 1; j < mSpace.size(); j++) { - Rect iRect = mSpace.get(i); - Rect jRect = mSpace.get(j); - if (jRect.contains(iRect)) { - removeRect(i); - i--; - break; - } - if (iRect.contains(jRect)) { - removeRect(j); - j--; - } - } - } - - if (DEBUG) { - mAllocated.add(r); - dumpImage(); - } - - return r; - } - - // DEBUGGING CODE: Enable with DEBUG - - private List<Rect> mAllocated; - private static int sLayoutId; - private static int sRectId; - private void dumpImage() { - if (DEBUG) { - int width = 100; - int height = 100; - for (Rect rect : mSpace) { - width = Math.max(width, rect.w); - height = Math.max(height, rect.h); - } - width += 10; - height += 10; - - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = image.createGraphics(); - g.setColor(Color.BLACK); - g.fillRect(0, 0, image.getWidth(), image.getHeight()); - - Color[] colors = new Color[] { - Color.blue, Color.cyan, - Color.green, Color.magenta, Color.orange, - Color.pink, Color.red, Color.white, Color.yellow, Color.darkGray, - Color.lightGray, Color.gray, - }; - - char allocated = 'A'; - for (Rect rect : mAllocated) { - Color color = new Color(0x9FFFFFFF, true); - g.setColor(color); - g.setBackground(color); - g.fillRect(rect.x, rect.y, rect.w, rect.h); - g.setColor(Color.WHITE); - g.drawRect(rect.x, rect.y, rect.w, rect.h); - g.drawString("" + (allocated++), - rect.x + rect.w / 2, rect.y + rect.h / 2); - } - - int colorIndex = 0; - for (Rect rect : mSpace) { - Color color = colors[colorIndex]; - colorIndex = (colorIndex + 1) % colors.length; - - color = new Color(color.getRed(), color.getGreen(), color.getBlue(), 128); - g.setColor(color); - - g.fillRect(rect.x, rect.y, rect.w, rect.h); - g.setColor(Color.WHITE); - g.drawString(Integer.toString(colorIndex), - rect.x + rect.w / 2, rect.y + rect.h / 2); - } - - - g.dispose(); - - File file = new File("/tmp/layout" + sLayoutId + "_pass" + sRectId + ".png"); - try { - ImageIO.write(image, "PNG", file); - System.out.println("Wrote diagnostics image " + file); - } catch (IOException e) { - e.printStackTrace(); - } - sRectId++; - } - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasAlternateSelection.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasAlternateSelection.java deleted file mode 100644 index c04061cbd..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasAlternateSelection.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import java.util.List; - -/** - * Information for the current alternate selection, i.e. the possible selected items - * that are located at the same x/y as the original view, either sibling or parents. - */ -/* package */ class CanvasAlternateSelection { - private final CanvasViewInfo mOriginatingView; - private final List<CanvasViewInfo> mAltViews; - private int mIndex; - - /** - * Creates a new alternate selection based on the given originating view and the - * given list of alternate views. Both cannot be null. - */ - public CanvasAlternateSelection(CanvasViewInfo originatingView, List<CanvasViewInfo> altViews) { - assert originatingView != null; - assert altViews != null; - mOriginatingView = originatingView; - mAltViews = altViews; - mIndex = altViews.size() - 1; - } - - /** Returns the list of alternate views. Cannot be null. */ - public List<CanvasViewInfo> getAltViews() { - return mAltViews; - } - - /** Returns the originating view. Cannot be null. */ - public CanvasViewInfo getOriginatingView() { - return mOriginatingView; - } - - /** - * Returns the current alternate view to select. - * Initially this is the top-most view. - */ - public CanvasViewInfo getCurrent() { - return mIndex >= 0 ? mAltViews.get(mIndex) : null; - } - - /** - * Changes the current view to be the next one and then returns it. - * This loops through the alternate views. - */ - public CanvasViewInfo getNext() { - if (mIndex == 0) { - mIndex = mAltViews.size() - 1; - } else if (mIndex > 0) { - mIndex--; - } - - return getCurrent(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java deleted file mode 100644 index ad5bd52e5..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE; - -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.widgets.ScrollBar; - -/** - * Helper class to convert between control pixel coordinates and canvas coordinates. - * Takes care of the zooming and offset of the canvas. - */ -public class CanvasTransform { - /** - * Default margin around the rendered image, reduced - * when the contents do not fit. - */ - public static final int DEFAULT_MARGIN = 25; - - /** - * The canvas which controls the zooming. - */ - private final LayoutCanvas mCanvas; - - /** Canvas image size (original, before zoom), in pixels. */ - private int mImgSize; - - /** Full size being scrolled (after zoom), in pixels */ - private int mFullSize;; - - /** Client size, in pixels. */ - private int mClientSize; - - /** Left-top offset in client pixel coordinates. */ - private int mTranslate; - - /** Current margin */ - private int mMargin = DEFAULT_MARGIN; - - /** Scaling factor, > 0. */ - private double mScale; - - /** Scrollbar widget. */ - private ScrollBar mScrollbar; - - public CanvasTransform(LayoutCanvas layoutCanvas, ScrollBar scrollbar) { - mCanvas = layoutCanvas; - mScrollbar = scrollbar; - mScale = 1.0; - mTranslate = 0; - - mScrollbar.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - // User requested scrolling. Changes translation and redraw canvas. - mTranslate = mScrollbar.getSelection(); - CanvasTransform.this.mCanvas.redraw(); - } - }); - mScrollbar.setIncrement(20); - } - - /** - * Sets the new scaling factor. Recomputes scrollbars. - * @param scale Scaling factor, > 0. - */ - public void setScale(double scale) { - if (mScale != scale) { - mScale = scale; - resizeScrollbar(); - } - } - - /** Recomputes the scrollbar and view port settings */ - public void refresh() { - resizeScrollbar(); - } - - /** - * Returns current scaling factor. - * - * @return The current scaling factor - */ - public double getScale() { - return mScale; - } - - /** - * Returns Canvas image size (original, before zoom), in pixels. - * - * @return Canvas image size (original, before zoom), in pixels - */ - public int getImgSize() { - return mImgSize; - } - - /** - * Returns the scaled image size in pixels. - * - * @return The scaled image size in pixels. - */ - public int getScaledImgSize() { - return (int) (mImgSize * mScale); - } - - /** - * Changes the size of the canvas image and the client size. Recomputes - * scrollbars. - * - * @param imgSize the size of the image being scaled - * @param fullSize the size of the full view area being scrolled - * @param clientSize the size of the view port - */ - public void setSize(int imgSize, int fullSize, int clientSize) { - mImgSize = imgSize; - mFullSize = fullSize; - mClientSize = clientSize; - mScrollbar.setPageIncrement(clientSize); - resizeScrollbar(); - } - - private void resizeScrollbar() { - // scaled image size - int sx = (int) (mScale * mFullSize); - - // Adjust margin such that for zoomed out views - // we don't waste space (unless the viewport is - // large enough to accommodate it) - int delta = mClientSize - sx; - if (delta < 0) { - mMargin = 0; - } else if (delta < 2 * DEFAULT_MARGIN) { - mMargin = delta / 2; - - ImageOverlay imageOverlay = mCanvas.getImageOverlay(); - if (imageOverlay != null && imageOverlay.getShowDropShadow() - && delta >= SHADOW_SIZE / 2) { - mMargin -= SHADOW_SIZE / 2; - // Add a little padding on the top too, if there's room. The shadow assets - // include enough padding on the bottom to not make this look clipped. - if (mMargin < 4) { - mMargin += 4; - } - } - } else { - mMargin = DEFAULT_MARGIN; - } - - if (mCanvas.getPreviewManager().hasPreviews()) { - // Make more room for the previews - mMargin = 2; - } - - // actual client area is always reduced by the margins - int cx = mClientSize - 2 * mMargin; - - if (sx < cx) { - mTranslate = 0; - mScrollbar.setEnabled(false); - } else { - mScrollbar.setEnabled(true); - - int selection = mScrollbar.getSelection(); - int thumb = cx; - int maximum = sx; - - if (selection + thumb > maximum) { - selection = maximum - thumb; - if (selection < 0) { - selection = 0; - } - } - - mScrollbar.setValues(selection, mScrollbar.getMinimum(), maximum, thumb, mScrollbar - .getIncrement(), mScrollbar.getPageIncrement()); - - mTranslate = selection; - } - } - - public int getMargin() { - return mMargin; - } - - public int translate(int canvasX) { - return mMargin - mTranslate + (int) (mScale * canvasX); - } - - public int scale(int canwasW) { - return (int) (mScale * canwasW); - } - - public int inverseTranslate(int screenX) { - return (int) ((screenX - mMargin + mTranslate) / mScale); - } - - public int inverseScale(int canwasW) { - return (int) (canwasW / mScale); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java deleted file mode 100644 index 03c6c3926..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java +++ /dev/null @@ -1,1178 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.FQCN_SPACE; -import static com.android.SdkConstants.FQCN_SPACE_V7; -import static com.android.SdkConstants.GESTURE_OVERLAY_VIEW; -import static com.android.SdkConstants.VIEW_MERGE; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Rect; -import com.android.ide.common.layout.GridLayoutRule; -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.common.rendering.api.MergeCookie; -import com.android.ide.common.rendering.api.ViewInfo; -import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.utils.Pair; - -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.ui.views.properties.IPropertyDescriptor; -import org.eclipse.ui.views.properties.IPropertySheetPage; -import org.eclipse.ui.views.properties.IPropertySource; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - * Maps a {@link ViewInfo} in a structure more adapted to our needs. - * The only large difference is that we keep both the original bounds of the view info - * and we pre-compute the selection bounds which are absolute to the rendered image - * (whereas the original bounds are relative to the parent view.) - * <p/> - * Each view also knows its parent and children. - * <p/> - * We can't alter {@link ViewInfo} as it is part of the LayoutBridge and needs to - * have a fixed API. - * <p/> - * The view info also implements {@link IPropertySource}, which enables a linked - * {@link IPropertySheetPage} to display the attributes of the selected element. - * This class actually delegates handling of {@link IPropertySource} to the underlying - * {@link UiViewElementNode}, if any. - */ -public class CanvasViewInfo implements IPropertySource { - - /** - * Minimal size of the selection, in case an empty view or layout is selected. - */ - public static final int SELECTION_MIN_SIZE = 6; - - private final Rectangle mAbsRect; - private final Rectangle mSelectionRect; - private final String mName; - private final Object mViewObject; - private final UiViewElementNode mUiViewNode; - private CanvasViewInfo mParent; - private ViewInfo mViewInfo; - private final List<CanvasViewInfo> mChildren = new ArrayList<CanvasViewInfo>(); - - /** - * Is this view info an individually exploded view? This is the case for views - * that were specially inflated by the {@link UiElementPullParser} and assigned - * fixed padding because they were invisible and somebody requested visibility. - */ - private boolean mExploded; - - /** - * Node sibling. This is usually null, but it's possible for a single node in the - * model to have <b>multiple</b> separate views in the canvas, for example - * when you {@code <include>} a view that has multiple widgets inside a - * {@code <merge>} tag. In this case, all the views have the same node model, - * the include tag, and selecting the include should highlight all the separate - * views that are linked to this node. That's what this field is all about: it is - * a <b>circular</b> list of all the siblings that share the same node. - */ - private List<CanvasViewInfo> mNodeSiblings; - - /** - * Constructs a {@link CanvasViewInfo} initialized with the given initial values. - */ - private CanvasViewInfo(CanvasViewInfo parent, String name, - Object viewObject, UiViewElementNode node, Rectangle absRect, - Rectangle selectionRect, ViewInfo viewInfo) { - mParent = parent; - mName = name; - mViewObject = viewObject; - mViewInfo = viewInfo; - mUiViewNode = node; - mAbsRect = absRect; - mSelectionRect = selectionRect; - } - - /** - * Returns the original {@link ViewInfo} bounds in absolute coordinates - * over the whole graphic. - * - * @return the bounding box in absolute coordinates - */ - @NonNull - public Rectangle getAbsRect() { - return mAbsRect; - } - - /** - * Returns the absolute selection bounds of the view info as a rectangle. - * The selection bounds will always have a size greater or equal to - * {@link #SELECTION_MIN_SIZE}. - * The width/height is inclusive (i.e. width = right-left-1). - * This is in absolute "screen" coordinates (relative to the rendered bitmap). - * - * @return the absolute selection bounds - */ - @NonNull - public Rectangle getSelectionRect() { - return mSelectionRect; - } - - /** - * Returns the view node. Could be null, although unlikely. - * @return An {@link UiViewElementNode} that uniquely identifies the object in the XML model. - * @see ViewInfo#getCookie() - */ - @Nullable - public UiViewElementNode getUiViewNode() { - return mUiViewNode; - } - - /** - * Returns the parent {@link CanvasViewInfo}. - * It is null for the root and non-null for children. - * - * @return the parent {@link CanvasViewInfo}, which can be null - */ - @Nullable - public CanvasViewInfo getParent() { - return mParent; - } - - /** - * Returns the list of children of this {@link CanvasViewInfo}. - * The list is never null. It can be empty. - * By contract, this.getChildren().get(0..n-1).getParent() == this. - * - * @return the children, never null - */ - @NonNull - public List<CanvasViewInfo> getChildren() { - return mChildren; - } - - /** - * For nodes that have multiple views rendered from a single node, such as the - * children of a {@code <merge>} tag included into a separate layout, return the - * "primary" view, the first view that is rendered - */ - @Nullable - private CanvasViewInfo getPrimaryNodeSibling() { - if (mNodeSiblings == null || mNodeSiblings.size() == 0) { - return null; - } - - return mNodeSiblings.get(0); - } - - /** - * Returns true if this view represents one view of many linked to a single node, and - * where this is the primary view. The primary view is the one that will be shown - * in the outline for example (since we only show nodes, not views, in the outline, - * and therefore don't want repetitions when a view has more than one view info.) - * - * @return true if this is the primary view among more than one linked to a single - * node - */ - private boolean isPrimaryNodeSibling() { - return getPrimaryNodeSibling() == this; - } - - /** - * Returns the list of node sibling of this view (which <b>will include this - * view</b>). For most views this is going to be null, but for views that share a - * single node (such as widgets inside a {@code <merge>} tag included into another - * layout), this will provide all the views that correspond to the node. - * - * @return a non-empty list of siblings (including this), or null - */ - @Nullable - public List<CanvasViewInfo> getNodeSiblings() { - return mNodeSiblings; - } - - /** - * Returns all the children of the canvas view info where each child corresponds to a - * unique node that the user can see and select. This is intended for use by the - * outline for example, where only the actual nodes are displayed, not the views - * themselves. - * <p> - * Most views have their own nodes, so this is generally the same as - * {@link #getChildren}, except in the case where you for example include a view that - * has multiple widgets inside a {@code <merge>} tag, where all these widgets have the - * same node (the {@code <merge>} tag). - * - * @return list of {@link CanvasViewInfo} objects that are children of this view, - * never null - */ - @NonNull - public List<CanvasViewInfo> getUniqueChildren() { - boolean haveHidden = false; - - for (CanvasViewInfo info : mChildren) { - if (info.mNodeSiblings != null) { - // We have secondary children; must create a new collection containing - // only non-secondary children - List<CanvasViewInfo> children = new ArrayList<CanvasViewInfo>(); - for (CanvasViewInfo vi : mChildren) { - if (vi.mNodeSiblings == null) { - children.add(vi); - } else if (vi.isPrimaryNodeSibling()) { - children.add(vi); - } - } - return children; - } - - haveHidden |= info.isHidden(); - } - - if (haveHidden) { - List<CanvasViewInfo> children = new ArrayList<CanvasViewInfo>(mChildren.size()); - for (CanvasViewInfo vi : mChildren) { - if (!vi.isHidden()) { - children.add(vi); - } - } - - return children; - } - - return mChildren; - } - - /** - * Returns true if the specific {@link CanvasViewInfo} is a parent - * of this {@link CanvasViewInfo}. It can be a direct parent or any - * grand-parent higher in the hierarchy. - * - * @param potentialParent the view info to check - * @return true if the given info is a parent of this view - */ - public boolean isParent(@NonNull CanvasViewInfo potentialParent) { - CanvasViewInfo p = mParent; - while (p != null) { - if (p == potentialParent) { - return true; - } - p = p.getParent(); - } - return false; - } - - /** - * Returns the name of the {@link CanvasViewInfo}. - * Could be null, although unlikely. - * Experience shows this is the full qualified Java name of the View. - * TODO: Rename this method to getFqcn. - * - * @return the name of the view info - * - * @see ViewInfo#getClassName() - */ - @NonNull - public String getName() { - return mName; - } - - /** - * Returns the View object associated with the {@link CanvasViewInfo}. - * @return the view object or null. - */ - @Nullable - public Object getViewObject() { - return mViewObject; - } - - /** - * Returns the baseline of this object, or -1 if it does not support a baseline - * - * @return the baseline or -1 - */ - public int getBaseline() { - if (mViewInfo != null) { - int baseline = mViewInfo.getBaseLine(); - if (baseline != Integer.MIN_VALUE) { - return baseline; - } - } - - return -1; - } - - /** - * Returns the {@link Margins} for this {@link CanvasViewInfo} - * - * @return the {@link Margins} for this {@link CanvasViewInfo} - */ - @Nullable - public Margins getMargins() { - if (mViewInfo != null) { - int leftMargin = mViewInfo.getLeftMargin(); - int topMargin = mViewInfo.getTopMargin(); - int rightMargin = mViewInfo.getRightMargin(); - int bottomMargin = mViewInfo.getBottomMargin(); - return new Margins( - leftMargin != Integer.MIN_VALUE ? leftMargin : 0, - rightMargin != Integer.MIN_VALUE ? rightMargin : 0, - topMargin != Integer.MIN_VALUE ? topMargin : 0, - bottomMargin != Integer.MIN_VALUE ? bottomMargin : 0 - ); - } - - return null; - } - - // ---- Implementation of IPropertySource - // TODO: Get rid of this once the old propertysheet implementation is fully gone - - @Override - public Object getEditableValue() { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - return ((IPropertySource) uiView).getEditableValue(); - } - return null; - } - - @Override - public IPropertyDescriptor[] getPropertyDescriptors() { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - return ((IPropertySource) uiView).getPropertyDescriptors(); - } - return null; - } - - @Override - public Object getPropertyValue(Object id) { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - return ((IPropertySource) uiView).getPropertyValue(id); - } - return null; - } - - @Override - public boolean isPropertySet(Object id) { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - return ((IPropertySource) uiView).isPropertySet(id); - } - return false; - } - - @Override - public void resetPropertyValue(Object id) { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - ((IPropertySource) uiView).resetPropertyValue(id); - } - } - - @Override - public void setPropertyValue(Object id, Object value) { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - ((IPropertySource) uiView).setPropertyValue(id, value); - } - } - - /** - * Returns the XML node corresponding to this info, or null if there is no - * such XML node. - * - * @return The XML node corresponding to this info object, or null - */ - @Nullable - public Node getXmlNode() { - UiViewElementNode uiView = getUiViewNode(); - if (uiView != null) { - return uiView.getXmlNode(); - } - - return null; - } - - /** - * Returns true iff this view info corresponds to a root element. - * - * @return True iff this is a root view info. - */ - public boolean isRoot() { - // Select the visual element -- unless it's the root. - // The root element is the one whose GRAND parent - // is null (because the parent will be a -document- - // node). - - // Special case: a gesture overlay is sometimes added as the root, but for all intents - // and purposes it is its layout child that is the real root so treat that one as the - // root as well (such that the whole layout canvas does not highlight as part of hovers - // etc) - if (mParent != null - && mParent.mName.endsWith(GESTURE_OVERLAY_VIEW) - && mParent.isRoot() - && mParent.mChildren.size() == 1) { - return true; - } - - return mUiViewNode == null || mUiViewNode.getUiParent() == null || - mUiViewNode.getUiParent().getUiParent() == null; - } - - /** - * Returns true if this {@link CanvasViewInfo} represents an invisible widget that - * should be highlighted when selected. This is the case for any layout that is less than the minimum - * threshold ({@link #SELECTION_MIN_SIZE}), or any other view that has -0- bounds. - * - * @return True if this is a tiny layout or invisible view - */ - public boolean isInvisible() { - if (isHidden()) { - // Don't expand and highlight hidden widgets - return false; - } - - if (mAbsRect.width < SELECTION_MIN_SIZE || mAbsRect.height < SELECTION_MIN_SIZE) { - return mUiViewNode != null && (mUiViewNode.getDescriptor().hasChildren() || - mAbsRect.width <= 0 || mAbsRect.height <= 0); - } - - return false; - } - - /** - * Returns true if this {@link CanvasViewInfo} represents a widget that should be - * hidden, such as a {@code <Space>} which are typically not manipulated by the user - * through dragging etc. - * - * @return true if this is a hidden view - */ - public boolean isHidden() { - if (GridLayoutRule.sDebugGridLayout) { - return false; - } - - return FQCN_SPACE.equals(mName) || FQCN_SPACE_V7.equals(mName); - } - - /** - * Is this {@link CanvasViewInfo} a view that has had its padding inflated in order to - * make it visible during selection or dragging? Note that this is NOT considered to - * be the case in the explode-all-views mode where all nodes have their padding - * increased; it's only used for views that individually exploded because they were - * requested visible and they returned true for {@link #isInvisible()}. - * - * @return True if this is an exploded node. - */ - public boolean isExploded() { - return mExploded; - } - - /** - * Mark this {@link CanvasViewInfo} as having been exploded or not. See the - * {@link #isExploded()} method for details on what this property means. - * - * @param exploded New value of the exploded property to mark this info with. - */ - void setExploded(boolean exploded) { - mExploded = exploded; - } - - /** - * Returns the info represented as a {@link SimpleElement}. - * - * @return A {@link SimpleElement} wrapping this info. - */ - @NonNull - SimpleElement toSimpleElement() { - - UiViewElementNode uiNode = getUiViewNode(); - - String fqcn = SimpleXmlTransfer.getFqcn(uiNode.getDescriptor()); - String parentFqcn = null; - Rect bounds = SwtUtils.toRect(getAbsRect()); - Rect parentBounds = null; - - UiElementNode uiParent = uiNode.getUiParent(); - if (uiParent != null) { - parentFqcn = SimpleXmlTransfer.getFqcn(uiParent.getDescriptor()); - } - if (getParent() != null) { - parentBounds = SwtUtils.toRect(getParent().getAbsRect()); - } - - SimpleElement e = new SimpleElement(fqcn, parentFqcn, bounds, parentBounds); - - for (UiAttributeNode attr : uiNode.getAllUiAttributes()) { - String value = attr.getCurrentValue(); - if (value != null && value.length() > 0) { - AttributeDescriptor attrDesc = attr.getDescriptor(); - SimpleAttribute a = new SimpleAttribute( - attrDesc.getNamespaceUri(), - attrDesc.getXmlLocalName(), - value); - e.addAttribute(a); - } - } - - for (CanvasViewInfo childVi : getChildren()) { - SimpleElement e2 = childVi.toSimpleElement(); - if (e2 != null) { - e.addInnerElement(e2); - } - } - - return e; - } - - /** - * Returns the layout url attribute value for the closest surrounding include or - * fragment element parent, or null if this {@link CanvasViewInfo} is not rendered as - * part of an include or fragment tag. - * - * @return the layout url attribute value for the surrounding include tag, or null if - * not applicable - */ - @Nullable - public String getIncludeUrl() { - CanvasViewInfo curr = this; - while (curr != null) { - if (curr.mUiViewNode != null) { - Node node = curr.mUiViewNode.getXmlNode(); - if (node != null && node.getNodeType() == Node.ELEMENT_NODE) { - String nodeName = node.getNodeName(); - if (node.getNamespaceURI() == null - && SdkConstants.VIEW_INCLUDE.equals(nodeName)) { - // Note: the layout attribute is NOT in the Android namespace - Element element = (Element) node; - String url = element.getAttribute(SdkConstants.ATTR_LAYOUT); - if (url.length() > 0) { - return url; - } - } else if (SdkConstants.VIEW_FRAGMENT.equals(nodeName)) { - String url = FragmentMenu.getFragmentLayout(node); - if (url != null) { - return url; - } - } - } - } - curr = curr.mParent; - } - - return null; - } - - /** Adds the given {@link CanvasViewInfo} as a new last child of this view */ - private void addChild(@NonNull CanvasViewInfo child) { - mChildren.add(child); - } - - /** Adds the given {@link CanvasViewInfo} as a child at the given index */ - private void addChildAt(int index, @NonNull CanvasViewInfo child) { - mChildren.add(index, child); - } - - /** - * Removes the given {@link CanvasViewInfo} from the child list of this view, and - * returns true if it was successfully removed - * - * @param child the child to be removed - * @return true if it was a child and was removed - */ - public boolean removeChild(@NonNull CanvasViewInfo child) { - return mChildren.remove(child); - } - - @Override - public String toString() { - return "CanvasViewInfo [name=" + mName + ", node=" + mUiViewNode + "]"; - } - - // ---- Factory functionality ---- - - /** - * Creates a new {@link CanvasViewInfo} hierarchy based on the given {@link ViewInfo} - * hierarchy. Note that this will not necessarily create one {@link CanvasViewInfo} - * for each {@link ViewInfo}. It will generally only create {@link CanvasViewInfo} - * objects for {@link ViewInfo} objects that contain a reference to an - * {@link UiViewElementNode}, meaning that it corresponds to an element in the XML - * file for this layout file. This is not always the case, such as in the following - * scenarios: - * <ul> - * <li>we link to other layouts with {@code <include>} - * <li>the current view is rendered within another view ("Show Included In") such that - * the outer file does not correspond to elements in the current included XML layout - * <li>on older platforms that don't support {@link Capability#EMBEDDED_LAYOUT} there - * is no reference to the {@code <include>} tag - * <li>with the {@code <merge>} tag we don't get a reference to the corresponding - * element - * <ul> - * <p> - * This method will build up a set of {@link CanvasViewInfo} that corresponds to the - * actual <b>selectable</b> views (which are also shown in the Outline). - * - * @param layoutlib5 if true, the {@link ViewInfo} hierarchy was created by layoutlib - * version 5 or higher, which means this algorithm can make certain assumptions - * (for example that {@code <merge>} siblings will provide {@link MergeCookie} - * references, so we don't have to search for them.) - * @param root the root {@link ViewInfo} to build from - * @return a {@link CanvasViewInfo} hierarchy - */ - @NonNull - public static Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root, boolean layoutlib5) { - return new Builder(layoutlib5).create(root); - } - - /** Builder object which walks over a tree of {@link ViewInfo} objects and builds - * up a corresponding {@link CanvasViewInfo} hierarchy. */ - private static class Builder { - public Builder(boolean layoutlib5) { - mLayoutLib5 = layoutlib5; - } - - /** - * The mapping from nodes that have a {@code <merge>} as a parent in the node - * model to their corresponding views - */ - private Map<UiViewElementNode, List<CanvasViewInfo>> mMergeNodeMap; - - /** - * Whether the ViewInfos are provided by a layout library that is version 5 or - * later, since that will allow us to take several shortcuts - */ - private boolean mLayoutLib5; - - /** - * Creates a hierarchy of {@link CanvasViewInfo} objects and merge bounding - * rectangles from the given {@link ViewInfo} hierarchy - */ - private Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root) { - Object cookie = root.getCookie(); - if (cookie == null) { - // Special case: If the root-most view does not have a view cookie, - // then we are rendering some outer layout surrounding this layout, and in - // that case we must search down the hierarchy for the (possibly multiple) - // sub-roots that correspond to elements in this layout, and place them inside - // an outer view that has no node. In the outline this item will be used to - // show the inclusion-context. - CanvasViewInfo rootView = createView(null, root, 0, 0); - addKeyedSubtrees(rootView, root, 0, 0); - - List<Rectangle> includedBounds = new ArrayList<Rectangle>(); - for (CanvasViewInfo vi : rootView.getChildren()) { - if (vi.getNodeSiblings() == null || vi.isPrimaryNodeSibling()) { - includedBounds.add(vi.getAbsRect()); - } - } - - // There are <merge> nodes here; see if we can insert it into the hierarchy - if (mMergeNodeMap != null) { - // Locate all the nodes that have a <merge> as a parent in the node model, - // and where the view sits at the top level inside the include-context node. - UiViewElementNode merge = null; - List<CanvasViewInfo> merged = new ArrayList<CanvasViewInfo>(); - for (Map.Entry<UiViewElementNode, List<CanvasViewInfo>> entry : mMergeNodeMap - .entrySet()) { - UiViewElementNode node = entry.getKey(); - if (!hasMergeParent(node)) { - continue; - } - List<CanvasViewInfo> views = entry.getValue(); - assert views.size() > 0; - CanvasViewInfo view = views.get(0); // primary - if (view.getParent() != rootView) { - continue; - } - UiElementNode parent = node.getUiParent(); - if (merge != null && parent != merge) { - continue; - } - merge = (UiViewElementNode) parent; - merged.add(view); - } - if (merged.size() > 0) { - // Compute a bounding box for the merged views - Rectangle absRect = null; - for (CanvasViewInfo child : merged) { - Rectangle rect = child.getAbsRect(); - if (absRect == null) { - absRect = rect; - } else { - absRect = absRect.union(rect); - } - } - - CanvasViewInfo mergeView = new CanvasViewInfo(rootView, VIEW_MERGE, null, - merge, absRect, absRect, null /* viewInfo */); - for (CanvasViewInfo view : merged) { - if (rootView.removeChild(view)) { - mergeView.addChild(view); - } - } - rootView.addChild(mergeView); - } - } - - return Pair.of(rootView, includedBounds); - } else { - // We have a view key at the top, so just go and create {@link CanvasViewInfo} - // objects for each {@link ViewInfo} until we run into a null key. - CanvasViewInfo rootView = addKeyedSubtrees(null, root, 0, 0); - - // Special case: look to see if the root element is really a <merge>, and if so, - // manufacture a view for it such that we can target this root element - // in drag & drop operations, such that we can show it in the outline, etc - if (rootView != null && hasMergeParent(rootView.getUiViewNode())) { - CanvasViewInfo merge = new CanvasViewInfo(null, VIEW_MERGE, null, - (UiViewElementNode) rootView.getUiViewNode().getUiParent(), - rootView.getAbsRect(), rootView.getSelectionRect(), - null /* viewInfo */); - // Insert the <merge> as the new real root - rootView.mParent = merge; - merge.addChild(rootView); - rootView = merge; - } - - return Pair.of(rootView, null); - } - } - - private boolean hasMergeParent(UiViewElementNode rootNode) { - UiElementNode rootParent = rootNode.getUiParent(); - return (rootParent instanceof UiViewElementNode - && VIEW_MERGE.equals(rootParent.getDescriptor().getXmlName())); - } - - /** Creates a {@link CanvasViewInfo} for a given {@link ViewInfo} but does not recurse */ - private CanvasViewInfo createView(CanvasViewInfo parent, ViewInfo root, int parentX, - int parentY) { - Object cookie = root.getCookie(); - UiViewElementNode node = null; - if (cookie instanceof UiViewElementNode) { - node = (UiViewElementNode) cookie; - } else if (cookie instanceof MergeCookie) { - cookie = ((MergeCookie) cookie).getCookie(); - if (cookie instanceof UiViewElementNode) { - node = (UiViewElementNode) cookie; - CanvasViewInfo view = createView(parent, root, parentX, parentY, node); - if (root.getCookie() instanceof MergeCookie && view.mNodeSiblings == null) { - List<CanvasViewInfo> v = mMergeNodeMap == null ? - null : mMergeNodeMap.get(node); - if (v != null) { - v.add(view); - } else { - v = new ArrayList<CanvasViewInfo>(); - v.add(view); - if (mMergeNodeMap == null) { - mMergeNodeMap = - new HashMap<UiViewElementNode, List<CanvasViewInfo>>(); - } - mMergeNodeMap.put(node, v); - } - view.mNodeSiblings = v; - } - - return view; - } - } - - return createView(parent, root, parentX, parentY, node); - } - - /** - * Creates a {@link CanvasViewInfo} for a given {@link ViewInfo} but does not recurse. - * This method specifies an explicit {@link UiViewElementNode} to use rather than - * relying on the view cookie in the info object. - */ - private CanvasViewInfo createView(CanvasViewInfo parent, ViewInfo root, int parentX, - int parentY, UiViewElementNode node) { - - int x = root.getLeft(); - int y = root.getTop(); - int w = root.getRight() - x; - int h = root.getBottom() - y; - - x += parentX; - y += parentY; - - Rectangle absRect = new Rectangle(x, y, w - 1, h - 1); - - if (w < SELECTION_MIN_SIZE) { - int d = (SELECTION_MIN_SIZE - w) / 2; - x -= d; - w += SELECTION_MIN_SIZE - w; - } - - if (h < SELECTION_MIN_SIZE) { - int d = (SELECTION_MIN_SIZE - h) / 2; - y -= d; - h += SELECTION_MIN_SIZE - h; - } - - Rectangle selectionRect = new Rectangle(x, y, w - 1, h - 1); - - return new CanvasViewInfo(parent, root.getClassName(), root.getViewObject(), node, - absRect, selectionRect, root); - } - - /** Create a subtree recursively until you run out of keys */ - private CanvasViewInfo createSubtree(CanvasViewInfo parent, ViewInfo viewInfo, - int parentX, int parentY) { - assert viewInfo.getCookie() != null; - - CanvasViewInfo view = createView(parent, viewInfo, parentX, parentY); - // Bug workaround: Ensure that we never have a child node identical - // to its parent node: this can happen for example when rendering a - // ZoomControls view where the merge cookies point to the parent. - if (parent != null && view.mUiViewNode == parent.mUiViewNode) { - return null; - } - - // Process children: - parentX += viewInfo.getLeft(); - parentY += viewInfo.getTop(); - - List<ViewInfo> children = viewInfo.getChildren(); - - if (mLayoutLib5) { - for (ViewInfo child : children) { - Object cookie = child.getCookie(); - if (cookie instanceof UiViewElementNode || cookie instanceof MergeCookie) { - CanvasViewInfo childView = createSubtree(view, child, - parentX, parentY); - if (childView != null) { - view.addChild(childView); - } - } // else: null cookies, adapter item references, etc: No child views. - } - - return view; - } - - // See if we have any missing keys at this level - int missingNodes = 0; - int mergeNodes = 0; - for (ViewInfo child : children) { - // Only use children which have a ViewKey of the correct type. - // We can't interact with those when they have a null key or - // an incompatible type. - Object cookie = child.getCookie(); - if (!(cookie instanceof UiViewElementNode)) { - if (cookie instanceof MergeCookie) { - mergeNodes++; - } else { - missingNodes++; - } - } - } - - if (missingNodes == 0 && mergeNodes == 0) { - // No missing nodes; this is the normal case, and we can just continue to - // recursively add our children - for (ViewInfo child : children) { - CanvasViewInfo childView = createSubtree(view, child, - parentX, parentY); - view.addChild(childView); - } - - // TBD: Emit placeholder views for keys that have no views? - } else { - // We don't have keys for one or more of the ViewInfos. There are many - // possible causes: we are on an SDK platform that does not support - // embedded_layout rendering, or we are including a view with a <merge> - // as the root element. - - UiViewElementNode uiViewNode = view.getUiViewNode(); - String containerName = uiViewNode != null - ? uiViewNode.getDescriptor().getXmlLocalName() : ""; //$NON-NLS-1$ - if (containerName.equals(SdkConstants.VIEW_INCLUDE)) { - // This is expected -- we don't WANT to get node keys for the content - // of an include since it's in a different file and should be treated - // as a single unit that cannot be edited (hence, no CanvasViewInfo - // children) - } else { - // We are getting children with null keys where we don't expect it; - // this usually means that we are dealing with an Android platform - // that does not support {@link Capability#EMBEDDED_LAYOUT}, or - // that there are <merge> tags which are doing surprising things - // to the view hierarchy - LinkedList<UiViewElementNode> unused = new LinkedList<UiViewElementNode>(); - if (uiViewNode != null) { - for (UiElementNode child : uiViewNode.getUiChildren()) { - if (child instanceof UiViewElementNode) { - unused.addLast((UiViewElementNode) child); - } - } - } - for (ViewInfo child : children) { - Object cookie = child.getCookie(); - if (mergeNodes > 0 && cookie instanceof MergeCookie) { - cookie = ((MergeCookie) cookie).getCookie(); - } - if (cookie != null) { - unused.remove(cookie); - } - } - - if (unused.size() > 0 || mergeNodes > 0) { - if (unused.size() == missingNodes) { - // The number of unmatched elements and ViewInfos are identical; - // it's very likely that they match one to one, so just use these - for (ViewInfo child : children) { - if (child.getCookie() == null) { - // Only create a flat (non-recursive) view - CanvasViewInfo childView = createView(view, child, parentX, - parentY, unused.removeFirst()); - view.addChild(childView); - } else { - CanvasViewInfo childView = createSubtree(view, child, parentX, - parentY); - view.addChild(childView); - } - } - } else { - // We have an uneven match. In this case we might be dealing - // with <merge> etc. - // We have no way to associate elements back with the - // corresponding <include> tags if there are more than one of - // them. That's not a huge tragedy since visually you are not - // allowed to edit these anyway; we just need to make a visual - // block for these for selection and outline purposes. - addMismatched(view, parentX, parentY, children, unused); - } - } else { - // No unused keys, but there are views without keys. - // We can't represent these since all views must have node keys - // such that you can operate on them. Just ignore these. - for (ViewInfo child : children) { - if (child.getCookie() != null) { - CanvasViewInfo childView = createSubtree(view, child, - parentX, parentY); - view.addChild(childView); - } - } - } - } - } - - return view; - } - - /** - * We have various {@link ViewInfo} children with null keys, and/or nodes in - * the corresponding UI model that are not referenced by any of the {@link ViewInfo} - * objects. This method attempts to account for this, by matching the views in - * the right order. - */ - private void addMismatched(CanvasViewInfo parentView, int parentX, int parentY, - List<ViewInfo> children, LinkedList<UiViewElementNode> unused) { - UiViewElementNode afterNode = null; - UiViewElementNode beforeNode = null; - // We have one important clue we can use when matching unused nodes - // with views: if we have a view V1 with node N1, and a view V2 with node N2, - // then we can only match unknown node UN with unknown node UV if - // V1 < UV < V2 and N1 < UN < N2. - // We can use these constraints to do the matching, for example by - // a simple DAG traversal. However, since the number of unmatched nodes - // will typically be very small, we'll just do a simple algorithm here - // which checks forwards/backwards whether a match is valid. - for (int index = 0, size = children.size(); index < size; index++) { - ViewInfo child = children.get(index); - if (child.getCookie() != null) { - CanvasViewInfo childView = createSubtree(parentView, child, parentX, parentY); - if (childView != null) { - parentView.addChild(childView); - } - if (child.getCookie() instanceof UiViewElementNode) { - afterNode = (UiViewElementNode) child.getCookie(); - } - } else { - beforeNode = nextViewNode(children, index); - - // Find first eligible node from unused - // TOD: What if there are more eligible? We need to process ALL views - // and all nodes in one go here - - UiViewElementNode matching = null; - for (UiViewElementNode candidate : unused) { - if (afterNode == null || isAfter(afterNode, candidate)) { - if (beforeNode == null || isBefore(beforeNode, candidate)) { - matching = candidate; - break; - } - } - } - - if (matching != null) { - unused.remove(matching); - CanvasViewInfo childView = createView(parentView, child, parentX, parentY, - matching); - parentView.addChild(childView); - afterNode = matching; - } else { - // We have no node for the view -- what do we do?? - // Nothing - we only represent stuff in the outline that is in the - // source model, not in the render - } - } - } - - // Add zero-bounded boxes for all remaining nodes since they need to show - // up in the outline, need to be selectable so you can press Delete, etc. - if (unused.size() > 0) { - Map<UiViewElementNode, Integer> rankMap = - new HashMap<UiViewElementNode, Integer>(); - Map<UiViewElementNode, CanvasViewInfo> infoMap = - new HashMap<UiViewElementNode, CanvasViewInfo>(); - UiElementNode parent = unused.get(0).getUiParent(); - if (parent != null) { - int index = 0; - for (UiElementNode child : parent.getUiChildren()) { - UiViewElementNode node = (UiViewElementNode) child; - rankMap.put(node, index++); - } - for (CanvasViewInfo child : parentView.getChildren()) { - infoMap.put(child.getUiViewNode(), child); - } - List<Integer> usedIndexes = new ArrayList<Integer>(); - for (UiViewElementNode node : unused) { - Integer rank = rankMap.get(node); - if (rank != null) { - usedIndexes.add(rank); - } - } - Collections.sort(usedIndexes); - for (int i = usedIndexes.size() - 1; i >= 0; i--) { - Integer rank = usedIndexes.get(i); - UiViewElementNode found = null; - for (UiViewElementNode node : unused) { - if (rankMap.get(node) == rank) { - found = node; - break; - } - } - if (found != null) { - Rectangle absRect = new Rectangle(parentX, parentY, 0, 0); - String name = found.getDescriptor().getXmlLocalName(); - CanvasViewInfo v = new CanvasViewInfo(parentView, name, null, found, - absRect, absRect, null /* viewInfo */); - // Find corresponding index in the parent view - List<CanvasViewInfo> siblings = parentView.getChildren(); - int insertPosition = siblings.size(); - for (int j = siblings.size() - 1; j >= 0; j--) { - CanvasViewInfo sibling = siblings.get(j); - UiViewElementNode siblingNode = sibling.getUiViewNode(); - if (siblingNode != null) { - Integer siblingRank = rankMap.get(siblingNode); - if (siblingRank != null && siblingRank < rank) { - insertPosition = j + 1; - break; - } - } - } - parentView.addChildAt(insertPosition, v); - unused.remove(found); - } - } - } - // Add in any remaining - for (UiViewElementNode node : unused) { - Rectangle absRect = new Rectangle(parentX, parentY, 0, 0); - String name = node.getDescriptor().getXmlLocalName(); - CanvasViewInfo v = new CanvasViewInfo(parentView, name, null, node, absRect, - absRect, null /* viewInfo */); - parentView.addChild(v); - } - } - } - - private boolean isBefore(UiViewElementNode beforeNode, UiViewElementNode candidate) { - UiElementNode parent = candidate.getUiParent(); - if (parent != null) { - for (UiElementNode sibling : parent.getUiChildren()) { - if (sibling == beforeNode) { - return false; - } else if (sibling == candidate) { - return true; - } - } - } - return false; - } - - private boolean isAfter(UiViewElementNode afterNode, UiViewElementNode candidate) { - UiElementNode parent = candidate.getUiParent(); - if (parent != null) { - for (UiElementNode sibling : parent.getUiChildren()) { - if (sibling == afterNode) { - return true; - } else if (sibling == candidate) { - return false; - } - } - } - return false; - } - - private UiViewElementNode nextViewNode(List<ViewInfo> children, int index) { - int size = children.size(); - for (; index < size; index++) { - ViewInfo child = children.get(index); - if (child.getCookie() instanceof UiViewElementNode) { - return (UiViewElementNode) child.getCookie(); - } - } - - return null; - } - - /** Search for a subtree with valid keys and add those subtrees */ - private CanvasViewInfo addKeyedSubtrees(CanvasViewInfo parent, ViewInfo viewInfo, - int parentX, int parentY) { - // We don't include MergeCookies when searching down for the first non-null key, - // since this means we are in a "Show Included In" context, and the include tag itself - // (which the merge cookie is pointing to) is still in the including-document rather - // than the included document. Therefore, we only accept real UiViewElementNodes here, - // not MergeCookies. - if (viewInfo.getCookie() != null) { - CanvasViewInfo subtree = createSubtree(parent, viewInfo, parentX, parentY); - if (parent != null && subtree != null) { - parent.mChildren.add(subtree); - } - return subtree; - } else { - for (ViewInfo child : viewInfo.getChildren()) { - addKeyedSubtrees(parent, child, parentX + viewInfo.getLeft(), parentY - + viewInfo.getTop()); - } - - return null; - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java deleted file mode 100644 index 263456984..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.ANDROID_NS_NAME; -import static com.android.SdkConstants.NS_RESOURCES; -import static com.android.SdkConstants.XMLNS_URI; - -import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.IDragElement.IDragAttribute; -import com.android.ide.common.api.INode; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; - -import org.eclipse.jface.action.Action; -import org.eclipse.swt.custom.StyledText; -import org.eclipse.swt.dnd.Clipboard; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.dnd.TransferData; -import org.eclipse.swt.widgets.Composite; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * The {@link ClipboardSupport} class manages the native clipboard, providing operations - * to copy, cut and paste view items, and can answer whether the clipboard contains - * a transferable we care about. - */ -public class ClipboardSupport { - private static final boolean DEBUG = false; - - /** SWT clipboard instance. */ - private Clipboard mClipboard; - private LayoutCanvas mCanvas; - - /** - * Constructs a new {@link ClipboardSupport} tied to the given - * {@link LayoutCanvas}. - * - * @param canvas The {@link LayoutCanvas} to provide clipboard support for. - * @param parent The parent widget in the SWT hierarchy of the canvas. - */ - public ClipboardSupport(LayoutCanvas canvas, Composite parent) { - mCanvas = canvas; - - mClipboard = new Clipboard(parent.getDisplay()); - } - - /** - * Frees up any resources held by the {@link ClipboardSupport}. - */ - public void dispose() { - if (mClipboard != null) { - mClipboard.dispose(); - mClipboard = null; - } - } - - /** - * Perform the "Copy" action, either from the Edit menu or from the context - * menu. - * <p/> - * This sanitizes the selection, so it must be a copy. It then inserts the - * selection both as text and as {@link SimpleElement}s in the clipboard. - * (If there is selected text in the error label, then the error is used - * as the text portion of the transferable.) - * - * @param selection A list of selection items to add to the clipboard; - * <b>this should be a copy already - this method will not make a - * copy</b> - */ - public void copySelectionToClipboard(List<SelectionItem> selection) { - SelectionManager.sanitize(selection); - - // The error message area shares the copy action with the canvas. Invoking the - // copy action when there are errors visible *AND* the user has selected text there, - // should include the error message as the text transferable. - String message = null; - GraphicalEditorPart graphicalEditor = mCanvas.getEditorDelegate().getGraphicalEditor(); - StyledText errorLabel = graphicalEditor.getErrorLabel(); - if (errorLabel.getSelectionCount() > 0) { - message = errorLabel.getSelectionText(); - } - - if (selection.isEmpty()) { - if (message != null) { - mClipboard.setContents( - new Object[] { message }, - new Transfer[] { TextTransfer.getInstance() } - ); - } - return; - } - - Object[] data = new Object[] { - SelectionItem.getAsElements(selection), - message != null ? message : SelectionItem.getAsText(mCanvas, selection) - }; - - Transfer[] types = new Transfer[] { - SimpleXmlTransfer.getInstance(), - TextTransfer.getInstance() - }; - - mClipboard.setContents(data, types); - } - - /** - * Perform the "Cut" action, either from the Edit menu or from the context - * menu. - * <p/> - * This sanitizes the selection, so it must be a copy. It uses the - * {@link #copySelectionToClipboard(List)} method to copy the selection to - * the clipboard. Finally it uses {@link #deleteSelection(String, List)} to - * delete the selection with a "Cut" verb for the title. - * - * @param selection A list of selection items to add to the clipboard; - * <b>this should be a copy already - this method will not make a - * copy</b> - */ - public void cutSelectionToClipboard(List<SelectionItem> selection) { - copySelectionToClipboard(selection); - deleteSelection(mCanvas.getCutLabel(), selection); - } - - /** - * Deletes the given selection. - * - * @param verb A translated verb for the action. Will be used for the - * undo/redo title. Typically this should be - * {@link Action#getText()} for either the cut or the delete - * actions in the canvas. - * @param selection The selection. Must not be null. Can be empty, in which - * case nothing happens. The selection list will be sanitized so - * the caller should pass in a copy. - */ - public void deleteSelection(String verb, final List<SelectionItem> selection) { - SelectionManager.sanitize(selection); - - if (selection.isEmpty()) { - return; - } - - // If all selected items have the same *kind* of parent, display that in the undo title. - String title = null; - for (SelectionItem cs : selection) { - CanvasViewInfo vi = cs.getViewInfo(); - if (vi != null && vi.getParent() != null) { - CanvasViewInfo parent = vi.getParent(); - assert parent != null; - if (title == null) { - title = parent.getName(); - } else if (!title.equals(parent.getName())) { - // More than one kind of parent selected. - title = null; - break; - } - } - } - - if (title != null) { - // Typically the name is an FQCN. Just get the last segment. - int pos = title.lastIndexOf('.'); - if (pos > 0 && pos < title.length() - 1) { - title = title.substring(pos + 1); - } - } - boolean multiple = mCanvas.getSelectionManager().hasMultiSelection(); - if (title == null) { - title = String.format( - multiple ? "%1$s elements" : "%1$s element", - verb); - } else { - title = String.format( - multiple ? "%1$s elements from %2$s" : "%1$s element from %2$s", - verb, title); - } - - // Implementation note: we don't clear the internal selection after removing - // the elements. An update XML model event should happen when the model gets released - // which will trigger a recompute of the layout, thus reloading the model thus - // resetting the selection. - mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel(title, new Runnable() { - @Override - public void run() { - // Segment the deleted nodes into clusters of siblings - Map<NodeProxy, List<INode>> clusters = - new HashMap<NodeProxy, List<INode>>(); - for (SelectionItem cs : selection) { - NodeProxy node = cs.getNode(); - if (node == null) { - continue; - } - INode parent = node.getParent(); - if (parent != null) { - List<INode> children = clusters.get(parent); - if (children == null) { - children = new ArrayList<INode>(); - clusters.put((NodeProxy) parent, children); - } - children.add(node); - } - } - - // Notify parent views about children getting deleted - RulesEngine rulesEngine = mCanvas.getRulesEngine(); - for (Map.Entry<NodeProxy, List<INode>> entry : clusters.entrySet()) { - NodeProxy parent = entry.getKey(); - List<INode> children = entry.getValue(); - assert children != null && children.size() > 0; - rulesEngine.callOnRemovingChildren(parent, children); - parent.applyPendingChanges(); - } - - for (SelectionItem cs : selection) { - CanvasViewInfo vi = cs.getViewInfo(); - // You can't delete the root element - if (vi != null && !vi.isRoot()) { - UiViewElementNode ui = vi.getUiViewNode(); - if (ui != null) { - ui.deleteXmlNode(); - } - } - } - } - }); - } - - /** - * Perform the "Paste" action, either from the Edit menu or from the context - * menu. - * - * @param selection A list of selection items to add to the clipboard; - * <b>this should be a copy already - this method will not make a - * copy</b> - */ - public void pasteSelection(List<SelectionItem> selection) { - - SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance(); - final SimpleElement[] pasted = (SimpleElement[]) mClipboard.getContents(sxt); - - if (pasted == null || pasted.length == 0) { - return; - } - - CanvasViewInfo lastRoot = mCanvas.getViewHierarchy().getRoot(); - if (lastRoot == null) { - // Pasting in an empty document. Only paste the first element. - pasteInEmptyDocument(pasted[0]); - return; - } - - // Otherwise use the current selection, if any, as a guide where to paste - // using the first selected element only. If there's no selection use - // the root as the insertion point. - SelectionManager.sanitize(selection); - final CanvasViewInfo target; - if (selection.size() > 0) { - SelectionItem cs = selection.get(0); - target = cs.getViewInfo(); - } else { - target = lastRoot; - } - - final NodeProxy targetNode = mCanvas.getNodeFactory().create(target); - mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel("Paste", new Runnable() { - @Override - public void run() { - RulesEngine engine = mCanvas.getRulesEngine(); - NodeProxy node = engine.callOnPaste(targetNode, target.getViewObject(), pasted); - node.applyPendingChanges(); - } - }); - } - - /** - * Paste a new root into an empty XML layout. - * <p/> - * In case of error (unknown FQCN, document not empty), silently do nothing. - * In case of success, the new element will have some default attributes set (xmlns:android, - * layout_width and height). The edit is wrapped in a proper undo. - * <p/> - * Implementation is similar to {@link #createDocumentRoot} except we also - * copy all the attributes and inner elements recursively. - */ - private void pasteInEmptyDocument(final IDragElement pastedElement) { - String rootFqcn = pastedElement.getFqcn(); - - // Need a valid empty document to create the new root - final LayoutEditorDelegate delegate = mCanvas.getEditorDelegate(); - final UiDocumentNode uiDoc = delegate.getUiRootNode(); - if (uiDoc == null || uiDoc.getUiChildren().size() > 0) { - debugPrintf("Failed to paste document root for %1$s: document is not empty", rootFqcn); - return; - } - - // Find the view descriptor matching our FQCN - final ViewElementDescriptor viewDesc = delegate.getFqcnViewDescriptor(rootFqcn); - if (viewDesc == null) { - // TODO this could happen if pasting a custom view not known in this project - debugPrintf("Failed to paste document root, unknown FQCN %1$s", rootFqcn); - return; - } - - // Get the last segment of the FQCN for the undo title - String title = rootFqcn; - int pos = title.lastIndexOf('.'); - if (pos > 0 && pos < title.length() - 1) { - title = title.substring(pos + 1); - } - title = String.format("Paste root %1$s in document", title); - - delegate.getEditor().wrapUndoEditXmlModel(title, new Runnable() { - @Override - public void run() { - UiElementNode uiNew = uiDoc.appendNewUiChild(viewDesc); - - // A root node requires the Android XMLNS - uiNew.setAttributeValue(ANDROID_NS_NAME, XMLNS_URI, NS_RESOURCES, - true /*override*/); - - // Copy all the attributes from the pasted element - for (IDragAttribute attr : pastedElement.getAttributes()) { - uiNew.setAttributeValue( - attr.getName(), - attr.getUri(), - attr.getValue(), - true /*override*/); - } - - // Adjust the attributes, adding the default layout_width/height - // only if they are not present (the original element should have - // them though.) - DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/); - - uiNew.createXmlNode(); - - // Now process all children - for (IDragElement childElement : pastedElement.getInnerElements()) { - addChild(uiNew, childElement); - } - } - - private void addChild(UiElementNode uiParent, IDragElement childElement) { - String childFqcn = childElement.getFqcn(); - final ViewElementDescriptor childDesc = - delegate.getFqcnViewDescriptor(childFqcn); - if (childDesc == null) { - // TODO this could happen if pasting a custom view - debugPrintf("Failed to paste element, unknown FQCN %1$s", childFqcn); - return; - } - - UiElementNode uiChild = uiParent.appendNewUiChild(childDesc); - - // Copy all the attributes from the pasted element - for (IDragAttribute attr : childElement.getAttributes()) { - uiChild.setAttributeValue( - attr.getName(), - attr.getUri(), - attr.getValue(), - true /*override*/); - } - - // Adjust the attributes, adding the default layout_width/height - // only if they are not present (the original element should have - // them though.) - DescriptorsUtils.setDefaultLayoutAttributes( - uiChild, false /*updateLayout*/); - - uiChild.createXmlNode(); - - // Now process all grand children - for (IDragElement grandChildElement : childElement.getInnerElements()) { - addChild(uiChild, grandChildElement); - } - } - }); - } - - /** - * Returns true if we have a a simple xml transfer data object on the - * clipboard. - * - * @return True if and only if the clipboard contains one of XML element - * objects. - */ - public boolean hasSxtOnClipboard() { - // The paste operation is only available if we can paste our custom type. - // We do not currently support pasting random text (e.g. XML). Maybe later. - SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance(); - for (TransferData td : mClipboard.getAvailableTypes()) { - if (sxt.isSupportedType(td)) { - return true; - } - } - - return false; - } - - private void debugPrintf(String message, Object... params) { - if (DEBUG) AdtPlugin.printToConsole("Clipboard", String.format(message, params)); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ControlPoint.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ControlPoint.java deleted file mode 100644 index 55930f6cd..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ControlPoint.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2010 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.dnd.DragSourceEvent; -import org.eclipse.swt.dnd.DragSourceListener; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.events.MenuDetectEvent; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.graphics.Point; - -/** - * A {@link ControlPoint} is a coordinate in the canvas control which corresponds - * exactly to (0,0) at the top left of the canvas. It is unaffected by canvas - * zooming. - */ -public final class ControlPoint { - /** Containing canvas which the point is relative to. */ - private final LayoutCanvas mCanvas; - - /** The X coordinate of the mouse coordinate. */ - public final int x; - - /** The Y coordinate of the mouse coordinate. */ - public final int y; - - /** - * Constructs a new {@link ControlPoint} from the given event. The event - * must be from a {@link MouseListener} associated with the - * {@link LayoutCanvas} such that the {@link MouseEvent#x} and - * {@link MouseEvent#y} fields are relative to the canvas. - * - * @param canvas The {@link LayoutCanvas} this point is within. - * @param event The mouse event to construct the {@link ControlPoint} - * from. - * @return A {@link ControlPoint} which corresponds to the given - * {@link MouseEvent}. - */ - public static ControlPoint create(LayoutCanvas canvas, MouseEvent event) { - // The mouse event coordinates should already be relative to the canvas - // widget. - assert event.widget == canvas : event.widget; - return new ControlPoint(canvas, event.x, event.y); - } - - /** - * Constructs a new {@link ControlPoint} from the given menu detect event. - * - * @param canvas The {@link LayoutCanvas} this point is within. - * @param event The menu detect event to construct the {@link ControlPoint} from. - * @return A {@link ControlPoint} which corresponds to the given - * {@link MenuDetectEvent}. - */ - public static ControlPoint create(LayoutCanvas canvas, MenuDetectEvent event) { - // The menu detect events are always display-relative. - org.eclipse.swt.graphics.Point p = canvas.toControl(event.x, event.y); - return new ControlPoint(canvas, p.x, p.y); - } - - /** - * Constructs a new {@link ControlPoint} from the given event. The event - * must be from a {@link DragSourceListener} associated with the - * {@link LayoutCanvas} such that the {@link DragSourceEvent#x} and - * {@link DragSourceEvent#y} fields are relative to the canvas. - * - * @param canvas The {@link LayoutCanvas} this point is within. - * @param event The mouse event to construct the {@link ControlPoint} - * from. - * @return A {@link ControlPoint} which corresponds to the given - * {@link DragSourceEvent}. - */ - public static ControlPoint create(LayoutCanvas canvas, DragSourceEvent event) { - // The drag source event coordinates should already be relative to the - // canvas widget. - return new ControlPoint(canvas, event.x, event.y); - } - - /** - * Constructs a new {@link ControlPoint} from the given event. - * - * @param canvas The {@link LayoutCanvas} this point is within. - * @param event The mouse event to construct the {@link ControlPoint} - * from. - * @return A {@link ControlPoint} which corresponds to the given - * {@link DropTargetEvent}. - */ - public static ControlPoint create(LayoutCanvas canvas, DropTargetEvent event) { - // The drop target events are always relative to the display, so we must - // first convert them to be canvas relative. - org.eclipse.swt.graphics.Point p = canvas.toControl(event.x, event.y); - return new ControlPoint(canvas, p.x, p.y); - } - - /** - * Constructs a new {@link ControlPoint} from the given x,y coordinates, - * which must be relative to the given {@link LayoutCanvas}. - * - * @param canvas The {@link LayoutCanvas} this point is within. - * @param x The mouse event x coordinate relative to the canvas - * @param y The mouse event x coordinate relative to the canvas - * @return A {@link ControlPoint} which corresponds to the given - * coordinates. - */ - public static ControlPoint create(LayoutCanvas canvas, int x, int y) { - return new ControlPoint(canvas, x, y); - } - - /** - * Constructs a new canvas control coordinate with the given X and Y - * coordinates. This is private; use one of the factory methods - * {@link #create(LayoutCanvas, MouseEvent)}, - * {@link #create(LayoutCanvas, DragSourceEvent)} or - * {@link #create(LayoutCanvas, DropTargetEvent)} instead. - * - * @param canvas The canvas which contains this coordinate - * @param x The mouse x coordinate - * @param y The mouse y coordinate - */ - private ControlPoint(LayoutCanvas canvas, int x, int y) { - mCanvas = canvas; - this.x = x; - this.y = y; - } - - /** - * Returns the equivalent {@link LayoutPoint} to this - * {@link ControlPoint}. - * - * @return The equivalent {@link LayoutPoint} to this - * {@link ControlPoint}. - */ - public LayoutPoint toLayout() { - int lx = mCanvas.getHorizontalTransform().inverseTranslate(x); - int ly = mCanvas.getVerticalTransform().inverseTranslate(y); - - return LayoutPoint.create(mCanvas, lx, ly); - } - - @Override - public String toString() { - return "ControlPoint [x=" + x + ", y=" + y + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + x; - result = prime * result + y; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ControlPoint other = (ControlPoint) obj; - if (x != other.x) - return false; - if (y != other.y) - return false; - if (mCanvas != other.mCanvas) { - return false; - } - return true; - } - - /** - * Returns this point as an SWT point in the display coordinate system - * - * @return this point as an SWT point in the display coordinate system - */ - public Point toDisplayPoint() { - return mCanvas.toDisplay(x, y); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java deleted file mode 100644 index 44cd0810f..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.NonNull; -import com.android.ide.common.resources.configuration.FolderConfiguration; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; -import com.android.resources.ResourceFolderType; -import com.google.common.base.Charsets; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.swt.widgets.Display; -import org.eclipse.ui.PartInitException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; - -/** Job which creates a new layout file for a given configuration */ -class CreateNewConfigJob extends Job { - private final GraphicalEditorPart mEditor; - private final IFile mFromFile; - private final FolderConfiguration mConfig; - - CreateNewConfigJob( - @NonNull GraphicalEditorPart editor, - @NonNull IFile fromFile, - @NonNull FolderConfiguration config) { - super("Create Alternate Layout"); - mEditor = editor; - mFromFile = fromFile; - mConfig = config; - } - - @Override - protected IStatus run(IProgressMonitor monitor) { - // get the folder name - String folderName = mConfig.getFolderName(ResourceFolderType.LAYOUT); - try { - // look to see if it exists. - // get the res folder - IFolder res = (IFolder) mFromFile.getParent().getParent(); - - IFolder newParentFolder = res.getFolder(folderName); - AdtUtils.ensureExists(newParentFolder); - final IFile file = newParentFolder.getFile(mFromFile.getName()); - if (file.exists()) { - String message = String.format("File 'res/%1$s/%2$s' already exists!", - folderName, mFromFile.getName()); - return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message); - } - - // Read current document contents instead of from file: mFromFile.getContents() - String text = mEditor.getEditorDelegate().getEditor().getStructuredDocument().get(); - ByteArrayInputStream input = new ByteArrayInputStream(text.getBytes(Charsets.UTF_8)); - file.create(input, false, monitor); - input.close(); - - // Ensure that the project resources updates itself to notice the new configuration. - // In theory, this shouldn't be necessary, but we need to make sure the - // resource manager knows about this immediately such that the call below - // to find the best configuration takes the new folder into account. - ResourceManager resourceManager = ResourceManager.getInstance(); - IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); - IFolder folder = root.getFolder(newParentFolder.getFullPath()); - resourceManager.getResourceFolder(folder); - - // Switch to the new file - Display display = mEditor.getConfigurationChooser().getDisplay(); - display.asyncExec(new Runnable() { - @Override - public void run() { - // The given old layout has been forked into a new layout - // for a given configuration. This means that the old layout - // is no longer a match for the configuration, which is - // probably what it is still showing. We have to modify - // its configuration to no longer be an impossible - // configuration. - ConfigurationChooser chooser = mEditor.getConfigurationChooser(); - chooser.onAlternateLayoutCreated(); - - // Finally open the new layout - try { - AdtPlugin.openFile(file, null, false); - } catch (PartInitException e) { - AdtPlugin.log(e, null); - } - } - }); - } catch (IOException e2) { - String message = String.format( - "Failed to create File 'res/%1$s/%2$s' : %3$s", - folderName, mFromFile.getName(), e2.getMessage()); - AdtPlugin.displayError("Layout Creation", message); - - return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, - message, e2); - } catch (CoreException e2) { - String message = String.format( - "Failed to create File 'res/%1$s/%2$s' : %3$s", - folderName, mFromFile.getName(), e2.getMessage()); - AdtPlugin.displayError("Layout Creation", message); - - return e2.getStatus(); - } - - return Status.OK_STATUS; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java deleted file mode 100644 index 1f97c8c54..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * 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.SdkConstants.CLASS_VIEW; -import static com.android.SdkConstants.CLASS_VIEWGROUP; -import static com.android.SdkConstants.FN_FRAMEWORK_LIBRARY; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.sdk.ProjectState; -import com.android.ide.eclipse.adt.internal.sdk.Sdk; -import com.android.utils.Pair; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jdt.core.Flags; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IMethod; -import org.eclipse.jdt.core.IPackageFragment; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.search.IJavaSearchConstants; -import org.eclipse.jdt.core.search.IJavaSearchScope; -import org.eclipse.jdt.core.search.SearchEngine; -import org.eclipse.jdt.core.search.SearchMatch; -import org.eclipse.jdt.core.search.SearchParticipant; -import org.eclipse.jdt.core.search.SearchPattern; -import org.eclipse.jdt.core.search.SearchRequestor; -import org.eclipse.jdt.internal.core.ResolvedBinaryType; -import org.eclipse.jdt.internal.core.ResolvedSourceType; -import org.eclipse.swt.widgets.Display; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * The {@link CustomViewFinder} can look up the custom views and third party views - * available for a given project. - */ -@SuppressWarnings("restriction") // JDT model access for custom-view class lookup -public class CustomViewFinder { - /** - * Qualified name for the per-project non-persistent property storing the - * {@link CustomViewFinder} for this project - */ - private final static QualifiedName CUSTOM_VIEW_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID, - "viewfinder"); //$NON-NLS-1$ - - /** Project that this view finder locates views for */ - private final IProject mProject; - - private final List<Listener> mListeners = new ArrayList<Listener>(); - - private List<String> mCustomViews; - private List<String> mThirdPartyViews; - private boolean mRefreshing; - - /** - * Constructs an {@link CustomViewFinder} for the given project. Don't use this method; - * use the {@link #get} factory method instead. - * - * @param project project to create an {@link CustomViewFinder} for - */ - private CustomViewFinder(IProject project) { - mProject = project; - } - - /** - * Returns the {@link CustomViewFinder} for the given project - * - * @param project the project the finder is associated with - * @return a {@CustomViewFinder} for the given project, never null - */ - public static CustomViewFinder get(IProject project) { - CustomViewFinder finder = null; - try { - finder = (CustomViewFinder) project.getSessionProperty(CUSTOM_VIEW_FINDER); - } catch (CoreException e) { - // Not a problem; we will just create a new one - } - - if (finder == null) { - finder = new CustomViewFinder(project); - try { - project.setSessionProperty(CUSTOM_VIEW_FINDER, finder); - } catch (CoreException e) { - AdtPlugin.log(e, "Can't store CustomViewFinder"); - } - } - - return finder; - } - - public void refresh() { - refresh(null /*listener*/, true /* sync */); - } - - public void refresh(final Listener listener) { - refresh(listener, false /* sync */); - } - - private void refresh(final Listener listener, boolean sync) { - // Add this listener to the list of listeners which should be notified when the - // search is done. (There could be more than one since multiple requests could - // arrive for a slow search since the search is run in a different thread). - if (listener != null) { - synchronized (this) { - mListeners.add(listener); - } - } - synchronized (this) { - if (listener != null) { - mListeners.add(listener); - } - if (mRefreshing) { - return; - } - mRefreshing = true; - } - - FindViewsJob job = new FindViewsJob(); - job.schedule(); - if (sync) { - try { - job.join(); - } catch (InterruptedException e) { - AdtPlugin.log(e, null); - } - } - } - - public Collection<String> getCustomViews() { - return mCustomViews == null ? null : Collections.unmodifiableCollection(mCustomViews); - } - - public Collection<String> getThirdPartyViews() { - return mThirdPartyViews == null - ? null : Collections.unmodifiableCollection(mThirdPartyViews); - } - - public Collection<String> getAllViews() { - // Not yet initialized: return null - if (mCustomViews == null) { - return null; - } - List<String> all = new ArrayList<String>(mCustomViews.size() + mThirdPartyViews.size()); - all.addAll(mCustomViews); - all.addAll(mThirdPartyViews); - return all; - } - - /** - * Returns a pair of view lists - the custom views and the 3rd-party views. - * This method performs no caching; it is the same as asking the custom view finder - * to refresh itself and then waiting for the answer and returning it. - * - * @param project the Android project - * @param layoutsOnly if true, only search for layouts - * @return a pair of lists, the first containing custom views and the second - * containing 3rd party views - */ - public static Pair<List<String>,List<String>> findViews( - final IProject project, boolean layoutsOnly) { - CustomViewFinder finder = get(project); - - return finder.findViews(layoutsOnly); - } - - private Pair<List<String>,List<String>> findViews(final boolean layoutsOnly) { - final Set<String> customViews = new HashSet<String>(); - final Set<String> thirdPartyViews = new HashSet<String>(); - - ProjectState state = Sdk.getProjectState(mProject); - final List<IProject> libraries = state != null - ? state.getFullLibraryProjects() : Collections.<IProject>emptyList(); - - SearchRequestor requestor = new SearchRequestor() { - @Override - public void acceptSearchMatch(SearchMatch match) throws CoreException { - // Ignore matches in comments - if (match.isInsideDocComment()) { - return; - } - - Object element = match.getElement(); - if (element instanceof ResolvedBinaryType) { - // Third party view - ResolvedBinaryType type = (ResolvedBinaryType) element; - IPackageFragment fragment = type.getPackageFragment(); - IPath path = fragment.getPath(); - String last = path.lastSegment(); - // Filter out android.jar stuff - if (last.equals(FN_FRAMEWORK_LIBRARY)) { - return; - } - if (!isValidView(type, layoutsOnly)) { - return; - } - - IProject matchProject = match.getResource().getProject(); - if (mProject == matchProject || libraries.contains(matchProject)) { - String fqn = type.getFullyQualifiedName(); - thirdPartyViews.add(fqn); - } - } else if (element instanceof ResolvedSourceType) { - // User custom view - IProject matchProject = match.getResource().getProject(); - if (mProject == matchProject || libraries.contains(matchProject)) { - ResolvedSourceType type = (ResolvedSourceType) element; - if (!isValidView(type, layoutsOnly)) { - return; - } - String fqn = type.getFullyQualifiedName(); - fqn = fqn.replace('$', '.'); - customViews.add(fqn); - } - } - } - }; - try { - IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject); - if (javaProject != null) { - String className = layoutsOnly ? CLASS_VIEWGROUP : CLASS_VIEW; - IType viewType = javaProject.findType(className); - if (viewType != null) { - IJavaSearchScope scope = SearchEngine.createHierarchyScope(viewType); - SearchParticipant[] participants = new SearchParticipant[] { - SearchEngine.getDefaultSearchParticipant() - }; - int matchRule = SearchPattern.R_PATTERN_MATCH | SearchPattern.R_CASE_SENSITIVE; - - SearchPattern pattern = SearchPattern.createPattern("*", - IJavaSearchConstants.CLASS, IJavaSearchConstants.IMPLEMENTORS, - matchRule); - SearchEngine engine = new SearchEngine(); - engine.search(pattern, participants, scope, requestor, - new NullProgressMonitor()); - } - } - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - - - List<String> custom = new ArrayList<String>(customViews); - List<String> thirdParty = new ArrayList<String>(thirdPartyViews); - - if (!layoutsOnly) { - // Update our cached answers (unless we were filtered on only layouts) - mCustomViews = custom; - mThirdPartyViews = thirdParty; - } - - return Pair.of(custom, thirdParty); - } - - /** - * Determines whether the given member is a valid android.view.View to be added to the - * list of custom views or third party views. It checks that the view is public and - * not abstract for example. - */ - private static boolean isValidView(IType type, boolean layoutsOnly) - throws JavaModelException { - // Skip anonymous classes - if (type.isAnonymous()) { - return false; - } - int flags = type.getFlags(); - if (Flags.isAbstract(flags) || !Flags.isPublic(flags)) { - return false; - } - - // TODO: if (layoutsOnly) perhaps try to filter out AdapterViews and other ViewGroups - // not willing to accept children via XML - - // See if the class has one of the acceptable constructors - // needed for XML instantiation: - // View(Context context) - // View(Context context, AttributeSet attrs) - // View(Context context, AttributeSet attrs, int defStyle) - // We don't simply do three direct checks via type.getMethod() because the types - // are not resolved, so we don't know for each parameter if we will get the - // fully qualified or the unqualified class names. - // Instead, iterate over the methods and look for a match. - String typeName = type.getElementName(); - for (IMethod method : type.getMethods()) { - // Only care about constructors - if (!method.getElementName().equals(typeName)) { - continue; - } - - String[] parameterTypes = method.getParameterTypes(); - if (parameterTypes == null || parameterTypes.length < 1 || parameterTypes.length > 3) { - continue; - } - - String first = parameterTypes[0]; - // Look for the parameter type signatures -- produced by - // JDT's Signature.createTypeSignature("Context", false /*isResolved*/);. - // This is not a typo; they were copy/pasted from the actual parameter names - // observed in the debugger examining these data structures. - if (first.equals("QContext;") //$NON-NLS-1$ - || first.equals("Qandroid.content.Context;")) { //$NON-NLS-1$ - if (parameterTypes.length == 1) { - return true; - } - String second = parameterTypes[1]; - if (second.equals("QAttributeSet;") //$NON-NLS-1$ - || second.equals("Qandroid.util.AttributeSet;")) { //$NON-NLS-1$ - if (parameterTypes.length == 2) { - return true; - } - String third = parameterTypes[2]; - if (third.equals("I")) { //$NON-NLS-1$ - if (parameterTypes.length == 3) { - return true; - } - } - } - } - } - - return false; - } - - /** - * Interface implemented by clients of the {@link CustomViewFinder} to be notified - * when a custom view search has completed. Will always be called on the SWT event - * dispatch thread. - */ - public interface Listener { - void viewsUpdated(Collection<String> customViews, Collection<String> thirdPartyViews); - } - - /** - * Job for performing class search off the UI thread. This is marked as a system job - * so that it won't show up in the progress monitor etc. - */ - private class FindViewsJob extends Job { - FindViewsJob() { - super("Find Custom Views"); - setSystem(true); - } - @Override - protected IStatus run(IProgressMonitor monitor) { - Pair<List<String>, List<String>> views = findViews(false); - mCustomViews = views.getFirst(); - mThirdPartyViews = views.getSecond(); - - // Notify listeners on SWT's UI thread - Display.getDefault().asyncExec(new Runnable() { - @Override - public void run() { - Collection<String> customViews = - Collections.unmodifiableCollection(mCustomViews); - Collection<String> thirdPartyViews = - Collections.unmodifiableCollection(mThirdPartyViews); - synchronized (this) { - for (Listener l : mListeners) { - l.viewsUpdated(customViews, thirdPartyViews); - } - mListeners.clear(); - mRefreshing = false; - } - } - }); - return Status.OK_STATUS; - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DelegatingAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DelegatingAction.java deleted file mode 100644 index 7a41b5b15..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DelegatingAction.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.NonNull; - -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IMenuCreator; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.util.IPropertyChangeListener; -import org.eclipse.swt.events.HelpListener; -import org.eclipse.swt.widgets.Event; - -/** - * Implementation of {@link IAction} which delegates to a different - * {@link IAction} which allows a subclass to wrap and customize some of the - * behavior of a different action - */ -public class DelegatingAction implements IAction { - private final IAction mAction; - - /** - * Construct a new delegate of the given action - * - * @param action the action to be delegated - */ - public DelegatingAction(@NonNull IAction action) { - mAction = action; - } - - @Override - public void addPropertyChangeListener(IPropertyChangeListener listener) { - mAction.addPropertyChangeListener(listener); - } - - @Override - public int getAccelerator() { - return mAction.getAccelerator(); - } - - @Override - public String getActionDefinitionId() { - return mAction.getActionDefinitionId(); - } - - @Override - public String getDescription() { - return mAction.getDescription(); - } - - @Override - public ImageDescriptor getDisabledImageDescriptor() { - return mAction.getDisabledImageDescriptor(); - } - - @Override - public HelpListener getHelpListener() { - return mAction.getHelpListener(); - } - - @Override - public ImageDescriptor getHoverImageDescriptor() { - return mAction.getHoverImageDescriptor(); - } - - @Override - public String getId() { - return mAction.getId(); - } - - @Override - public ImageDescriptor getImageDescriptor() { - return mAction.getImageDescriptor(); - } - - @Override - public IMenuCreator getMenuCreator() { - return mAction.getMenuCreator(); - } - - @Override - public int getStyle() { - return mAction.getStyle(); - } - - @Override - public String getText() { - return mAction.getText(); - } - - @Override - public String getToolTipText() { - return mAction.getToolTipText(); - } - - @Override - public boolean isChecked() { - return mAction.isChecked(); - } - - @Override - public boolean isEnabled() { - return mAction.isEnabled(); - } - - @Override - public boolean isHandled() { - return mAction.isHandled(); - } - - @Override - public void removePropertyChangeListener(IPropertyChangeListener listener) { - mAction.removePropertyChangeListener(listener); - } - - @Override - public void run() { - mAction.run(); - } - - @Override - public void runWithEvent(Event event) { - mAction.runWithEvent(event); - } - - @Override - public void setActionDefinitionId(String id) { - mAction.setActionDefinitionId(id); - } - - @Override - public void setChecked(boolean checked) { - mAction.setChecked(checked); - } - - @Override - public void setDescription(String text) { - mAction.setDescription(text); - } - - @Override - public void setDisabledImageDescriptor(ImageDescriptor newImage) { - mAction.setDisabledImageDescriptor(newImage); - } - - @Override - public void setEnabled(boolean enabled) { - mAction.setEnabled(enabled); - } - - @Override - public void setHelpListener(HelpListener listener) { - mAction.setHelpListener(listener); - } - - @Override - public void setHoverImageDescriptor(ImageDescriptor newImage) { - mAction.setHoverImageDescriptor(newImage); - } - - @Override - public void setId(String id) { - mAction.setId(id); - } - - @Override - public void setImageDescriptor(ImageDescriptor newImage) { - mAction.setImageDescriptor(newImage); - } - - @Override - public void setMenuCreator(IMenuCreator creator) { - mAction.setMenuCreator(creator); - } - - @Override - public void setText(String text) { - mAction.setText(text); - } - - @Override - public void setToolTipText(String text) { - mAction.setToolTipText(text); - } - - @Override - public void setAccelerator(int keycode) { - mAction.setAccelerator(keycode); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java deleted file mode 100644 index 145036bf3..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java +++ /dev/null @@ -1,915 +0,0 @@ -/* - * 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.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ID_PREFIX; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.SdkConstants.TOOLS_URI; -import static org.eclipse.wst.xml.core.internal.provisional.contenttype.ContentTypeIdForXML.ContentTypeID_XML; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; -import com.android.utils.Pair; - -import org.eclipse.core.resources.IFile; -import org.eclipse.jface.text.IDocument; -import org.eclipse.wst.sse.core.StructuredModelManager; -import org.eclipse.wst.sse.core.internal.provisional.IModelManager; -import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; -import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; -import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; -import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; -import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -/** - * Various utility methods for manipulating DOM nodes. - */ -@SuppressWarnings("restriction") // No replacement for restricted XML model yet -public class DomUtilities { - /** - * Finds the nearest common parent of the two given nodes (which could be one of the - * two nodes as well) - * - * @param node1 the first node to test - * @param node2 the second node to test - * @return the nearest common parent of the two given nodes - */ - @Nullable - public static Node getCommonAncestor(@NonNull Node node1, @NonNull Node node2) { - while (node2 != null) { - Node current = node1; - while (current != null && current != node2) { - current = current.getParentNode(); - } - if (current == node2) { - return current; - } - node2 = node2.getParentNode(); - } - - return null; - } - - /** - * Returns all elements below the given node (which can be a document, - * element, etc). This will include the node itself, if it is an element. - * - * @param node the node to search from - * @return all elements in the subtree formed by the node parameter - */ - @NonNull - public static List<Element> getAllElements(@NonNull Node node) { - List<Element> elements = new ArrayList<Element>(64); - addElements(node, elements); - return elements; - } - - private static void addElements(@NonNull Node node, @NonNull List<Element> elements) { - if (node instanceof Element) { - elements.add((Element) node); - } - - NodeList childNodes = node.getChildNodes(); - for (int i = 0, n = childNodes.getLength(); i < n; i++) { - addElements(childNodes.item(i), elements); - } - } - - /** - * Returns the depth of the given node (with the document node having depth 0, - * and the document element having depth 1) - * - * @param node the node to test - * @return the depth in the document - */ - public static int getDepth(@NonNull Node node) { - int depth = -1; - while (node != null) { - depth++; - node = node.getParentNode(); - } - - return depth; - } - - /** - * Returns true if the given node has one or more element children - * - * @param node the node to test for element children - * @return true if the node has one or more element children - */ - public static boolean hasElementChildren(@NonNull Node node) { - NodeList children = node.getChildNodes(); - for (int i = 0, n = children.getLength(); i < n; i++) { - if (children.item(i).getNodeType() == Node.ELEMENT_NODE) { - return true; - } - } - - return false; - } - - /** - * Returns the DOM document for the given file - * - * @param file the XML file - * @return the document, or null if not found or not parsed properly (no - * errors are generated/thrown) - */ - @Nullable - public static Document getDocument(@NonNull IFile file) { - IModelManager modelManager = StructuredModelManager.getModelManager(); - if (modelManager == null) { - return null; - } - try { - IStructuredModel model = modelManager.getExistingModelForRead(file); - if (model == null) { - model = modelManager.getModelForRead(file); - } - if (model != null) { - if (model instanceof IDOMModel) { - IDOMModel domModel = (IDOMModel) model; - return domModel.getDocument(); - } - try { - } finally { - model.releaseFromRead(); - } - } - } catch (Exception e) { - // Ignore exceptions. - } - - return null; - } - - /** - * Returns the DOM document for the given editor - * - * @param editor the XML editor - * @return the document, or null if not found or not parsed properly (no - * errors are generated/thrown) - */ - @Nullable - public static Document getDocument(@NonNull AndroidXmlEditor editor) { - IStructuredModel model = editor.getModelForRead(); - try { - if (model instanceof IDOMModel) { - IDOMModel domModel = (IDOMModel) model; - return domModel.getDocument(); - } - } finally { - if (model != null) { - model.releaseFromRead(); - } - } - - return null; - } - - - /** - * Returns the XML DOM node corresponding to the given offset of the given - * document. - * - * @param document The document to look in - * @param offset The offset to look up the node for - * @return The node containing the offset, or null - */ - @Nullable - public static Node getNode(@NonNull IDocument document, int offset) { - Node node = null; - IModelManager modelManager = StructuredModelManager.getModelManager(); - if (modelManager == null) { - return null; - } - try { - IStructuredModel model = modelManager.getExistingModelForRead(document); - if (model != null) { - try { - for (; offset >= 0 && node == null; --offset) { - node = (Node) model.getIndexedRegion(offset); - } - } finally { - model.releaseFromRead(); - } - } - } catch (Exception e) { - // Ignore exceptions. - } - - return node; - } - - /** - * Returns the editing context at the given offset, as a pair of parent node and child - * node. This is not the same as just calling {@link DomUtilities#getNode} and taking - * its parent node, because special care has to be taken to return content element - * positions. - * <p> - * For example, for the XML {@code <foo>^</foo>}, if the caret ^ is inside the foo - * element, between the opening and closing tags, then the foo element is the parent, - * and the child is null which represents a potential text node. - * <p> - * If the node is inside an element tag definition (between the opening and closing - * bracket) then the child node will be the element and whatever parent (element or - * document) will be its parent. - * <p> - * If the node is in a text node, then the text node will be the child and its parent - * element or document node its parent. - * <p> - * Finally, if the caret is on a boundary of a text node, then the text node will be - * considered the child, regardless of whether it is on the left or right of the - * caret. For example, in the XML {@code <foo>^ </foo>} and in the XML - * {@code <foo> ^</foo>}, in both cases the text node is preferred over the element. - * - * @param document the document to search in - * @param offset the offset to look up - * @return a pair of parent and child elements, where either the parent or the child - * but not both can be null, and if non null the child.getParentNode() should - * return the parent. Note that the method can also return null if no - * document or model could be obtained or if the offset is invalid. - */ - @Nullable - public static Pair<Node, Node> getNodeContext(@NonNull IDocument document, int offset) { - Node node = null; - IModelManager modelManager = StructuredModelManager.getModelManager(); - if (modelManager == null) { - return null; - } - try { - IStructuredModel model = modelManager.getExistingModelForRead(document); - if (model != null) { - try { - for (; offset >= 0 && node == null; --offset) { - IndexedRegion indexedRegion = model.getIndexedRegion(offset); - if (indexedRegion != null) { - node = (Node) indexedRegion; - - if (node.getNodeType() == Node.TEXT_NODE) { - return Pair.of(node.getParentNode(), node); - } - - // Look at the structured document to see if - // we have the special case where the caret is pointing at - // a -potential- text node, e.g. <foo>^</foo> - IStructuredDocument doc = model.getStructuredDocument(); - IStructuredDocumentRegion region = - doc.getRegionAtCharacterOffset(offset); - - ITextRegion subRegion = region.getRegionAtCharacterOffset(offset); - String type = subRegion.getType(); - if (DOMRegionContext.XML_END_TAG_OPEN.equals(type)) { - // Try to return the text node if it's on the left - // of this element node, such that replace strings etc - // can be computed. - Node lastChild = node.getLastChild(); - if (lastChild != null) { - IndexedRegion previousRegion = (IndexedRegion) lastChild; - if (previousRegion.getEndOffset() == offset) { - return Pair.of(node, lastChild); - } - } - return Pair.of(node, null); - } - - return Pair.of(node.getParentNode(), node); - } - } - } finally { - model.releaseFromRead(); - } - } - } catch (Exception e) { - // Ignore exceptions. - } - - return null; - } - - /** - * Like {@link #getNode(IDocument, int)}, but has a bias parameter which lets you - * indicate whether you want the search to look forwards or backwards. - * This is vital when trying to compute a node range. Consider the following - * XML fragment: - * {@code - * <a/><b/>[<c/><d/><e/>]<f/><g/> - * } - * Suppose we want to locate the nodes in the range indicated by the brackets above. - * If we want to search for the node corresponding to the start position, should - * we pick the node on its left or the node on its right? Similarly for the end - * position. Clearly, we'll need to bias the search towards the right when looking - * for the start position, and towards the left when looking for the end position. - * The following method lets us do just that. When passed an offset which sits - * on the edge of the computed node, it will pick the neighbor based on whether - * "forward" is true or false, where forward means searching towards the right - * and not forward is obviously towards the left. - * @param document the document to search in - * @param offset the offset to search for - * @param forward if true, search forwards, otherwise search backwards when on node boundaries - * @return the node which surrounds the given offset, or the node adjacent to the offset - * where the side depends on the forward parameter - */ - @Nullable - public static Node getNode(@NonNull IDocument document, int offset, boolean forward) { - Node node = getNode(document, offset); - - if (node instanceof IndexedRegion) { - IndexedRegion region = (IndexedRegion) node; - - if (!forward && offset <= region.getStartOffset()) { - Node left = node.getPreviousSibling(); - if (left == null) { - left = node.getParentNode(); - } - - node = left; - } else if (forward && offset >= region.getEndOffset()) { - Node right = node.getNextSibling(); - if (right == null) { - right = node.getParentNode(); - } - node = right; - } - } - - return node; - } - - /** - * Returns a range of elements for the given caret range. Note that the two elements - * may not be at the same level so callers may want to perform additional input - * filtering. - * - * @param document the document to search in - * @param beginOffset the beginning offset of the range - * @param endOffset the ending offset of the range - * @return a pair of begin+end elements, or null - */ - @Nullable - public static Pair<Element, Element> getElementRange(@NonNull IDocument document, - int beginOffset, int endOffset) { - Element beginElement = null; - Element endElement = null; - Node beginNode = getNode(document, beginOffset, true); - Node endNode = beginNode; - if (endOffset > beginOffset) { - endNode = getNode(document, endOffset, false); - } - - if (beginNode == null || endNode == null) { - return null; - } - - // Adjust offsets if you're pointing at text - if (beginNode.getNodeType() != Node.ELEMENT_NODE) { - // <foo> <bar1/> | <bar2/> </foo> => should pick <bar2/> - beginElement = getNextElement(beginNode); - if (beginElement == null) { - // Might be inside the end of a parent, e.g. - // <foo> <bar/> | </foo> => should pick <bar/> - beginElement = getPreviousElement(beginNode); - if (beginElement == null) { - // We must be inside an empty element, - // <foo> | </foo> - // In that case just pick the parent. - beginElement = getParentElement(beginNode); - } - } - } else { - beginElement = (Element) beginNode; - } - - if (endNode.getNodeType() != Node.ELEMENT_NODE) { - // In the following, | marks the caret position: - // <foo> <bar1/> | <bar2/> </foo> => should pick <bar1/> - endElement = getPreviousElement(endNode); - if (endElement == null) { - // Might be inside the beginning of a parent, e.g. - // <foo> | <bar/></foo> => should pick <bar/> - endElement = getNextElement(endNode); - if (endElement == null) { - // We must be inside an empty element, - // <foo> | </foo> - // In that case just pick the parent. - endElement = getParentElement(endNode); - } - } - } else { - endElement = (Element) endNode; - } - - if (beginElement != null && endElement != null) { - return Pair.of(beginElement, endElement); - } - - return null; - } - - /** - * Returns the next sibling element of the node, or null if there is no such element - * - * @param node the starting node - * @return the next sibling element, or null - */ - @Nullable - public static Element getNextElement(@NonNull Node node) { - while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { - node = node.getNextSibling(); - } - - return (Element) node; // may be null as well - } - - /** - * Returns the previous sibling element of the node, or null if there is no such element - * - * @param node the starting node - * @return the previous sibling element, or null - */ - @Nullable - public static Element getPreviousElement(@NonNull Node node) { - while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { - node = node.getPreviousSibling(); - } - - return (Element) node; // may be null as well - } - - /** - * Returns the closest ancestor element, or null if none - * - * @param node the starting node - * @return the closest parent element, or null - */ - @Nullable - public static Element getParentElement(@NonNull Node node) { - while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { - node = node.getParentNode(); - } - - return (Element) node; // may be null as well - } - - /** Utility used by {@link #getFreeWidgetId(Element)} */ - private static void addLowercaseIds(@NonNull Element root, @NonNull Set<String> seen) { - if (root.hasAttributeNS(ANDROID_URI, ATTR_ID)) { - String id = root.getAttributeNS(ANDROID_URI, ATTR_ID); - if (id.startsWith(NEW_ID_PREFIX)) { - // See getFreeWidgetId for details on locale - seen.add(id.substring(NEW_ID_PREFIX.length()).toLowerCase(Locale.US)); - } else if (id.startsWith(ID_PREFIX)) { - seen.add(id.substring(ID_PREFIX.length()).toLowerCase(Locale.US)); - } else { - seen.add(id.toLowerCase(Locale.US)); - } - } - } - - /** - * Returns a suitable new widget id (not including the {@code @id/} prefix) for the - * given element, which is guaranteed to be unique in this document - * - * @param element the element to compute a new widget id for - * @param reserved an optional set of extra, "reserved" set of ids that should be - * considered taken - * @param prefix an optional prefix to use for the generated name, or null to get a - * default (which is currently the tag name) - * @return a unique id, never null, which does not include the {@code @id/} prefix - * @see DescriptorsUtils#getFreeWidgetId - */ - public static String getFreeWidgetId( - @NonNull Element element, - @Nullable Set<String> reserved, - @Nullable String prefix) { - Set<String> ids = new HashSet<String>(); - if (reserved != null) { - for (String id : reserved) { - // Note that we perform locale-independent lowercase checks; in "Image" we - // want the lowercase version to be "image", not "?mage" where ? is - // the char LATIN SMALL LETTER DOTLESS I. - - ids.add(id.toLowerCase(Locale.US)); - } - } - addLowercaseIds(element.getOwnerDocument().getDocumentElement(), ids); - - if (prefix == null) { - prefix = DescriptorsUtils.getBasename(element.getTagName()); - } - String generated; - int num = 1; - do { - generated = String.format("%1$s%2$d", prefix, num++); //$NON-NLS-1$ - } while (ids.contains(generated.toLowerCase(Locale.US))); - - return generated; - } - - /** - * Returns the element children of the given element - * - * @param element the parent element - * @return a list of child elements, possibly empty but never null - */ - @NonNull - public static List<Element> getChildren(@NonNull Element element) { - // Convenience to avoid lots of ugly DOM access casting - NodeList children = element.getChildNodes(); - // An iterator would have been more natural (to directly drive the child list - // iteration) but iterators can't be used in enhanced for loops... - List<Element> result = new ArrayList<Element>(children.getLength()); - for (int i = 0, n = children.getLength(); i < n; i++) { - Node node = children.item(i); - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element child = (Element) node; - result.add(child); - } - } - - return result; - } - - /** - * Returns true iff the given elements are contiguous siblings - * - * @param elements the elements to be tested - * @return true if the elements are contiguous siblings with no gaps - */ - public static boolean isContiguous(@NonNull List<Element> elements) { - if (elements.size() > 1) { - // All elements must be siblings (e.g. same parent) - Node parent = elements.get(0).getParentNode(); - if (!(parent instanceof Element)) { - return false; - } - for (Element node : elements) { - if (parent != node.getParentNode()) { - return false; - } - } - - // Ensure that the siblings are contiguous; no gaps. - // If we've selected all the children of the parent then we don't need - // to look. - List<Element> siblings = DomUtilities.getChildren((Element) parent); - if (siblings.size() != elements.size()) { - Set<Element> nodeSet = new HashSet<Element>(elements); - boolean inRange = false; - int remaining = elements.size(); - for (Element node : siblings) { - boolean in = nodeSet.contains(node); - if (in) { - remaining--; - if (remaining == 0) { - break; - } - inRange = true; - } else if (inRange) { - return false; - } - } - } - } - - return true; - } - - /** - * Determines whether two element trees are equivalent. Two element trees are - * equivalent if they represent the same DOM structure (elements, attributes, and - * children in order). This is almost the same as simply checking whether the String - * representations of the two nodes are identical, but this allows for minor - * variations that are not semantically significant, such as variations in formatting - * or ordering of the element attribute declarations, and the text children are - * ignored (this is such that in for example layout where content is only used for - * indentation the indentation differences are ignored). Null trees are never equal. - * - * @param element1 the first element to compare - * @param element2 the second element to compare - * @return true if the two element hierarchies are logically equal - */ - public static boolean isEquivalent(@Nullable Element element1, @Nullable Element element2) { - if (element1 == null || element2 == null) { - return false; - } - - if (!element1.getTagName().equals(element2.getTagName())) { - return false; - } - - // Check attribute map - NamedNodeMap attributes1 = element1.getAttributes(); - NamedNodeMap attributes2 = element2.getAttributes(); - - List<Attr> attributeNodes1 = new ArrayList<Attr>(); - for (int i = 0, n = attributes1.getLength(); i < n; i++) { - Attr attribute = (Attr) attributes1.item(i); - // Ignore tools uri namespace attributes for equivalency test - if (TOOLS_URI.equals(attribute.getNamespaceURI())) { - continue; - } - attributeNodes1.add(attribute); - } - List<Attr> attributeNodes2 = new ArrayList<Attr>(); - for (int i = 0, n = attributes2.getLength(); i < n; i++) { - Attr attribute = (Attr) attributes2.item(i); - // Ignore tools uri namespace attributes for equivalency test - if (TOOLS_URI.equals(attribute.getNamespaceURI())) { - continue; - } - attributeNodes2.add(attribute); - } - - if (attributeNodes1.size() != attributeNodes2.size()) { - return false; - } - - if (attributes1.getLength() > 0) { - Collections.sort(attributeNodes1, ATTRIBUTE_COMPARATOR); - Collections.sort(attributeNodes2, ATTRIBUTE_COMPARATOR); - for (int i = 0; i < attributeNodes1.size(); i++) { - Attr attr1 = attributeNodes1.get(i); - Attr attr2 = attributeNodes2.get(i); - if (attr1.getLocalName() == null || attr2.getLocalName() == null) { - if (!attr1.getName().equals(attr2.getName())) { - return false; - } - } else if (!attr1.getLocalName().equals(attr2.getLocalName())) { - return false; - } - if (!attr1.getValue().equals(attr2.getValue())) { - return false; - } - if (attr1.getNamespaceURI() == null) { - if (attr2.getNamespaceURI() != null) { - return false; - } - } else if (attr2.getNamespaceURI() == null) { - return false; - } else if (!attr1.getNamespaceURI().equals(attr2.getNamespaceURI())) { - return false; - } - } - } - - NodeList children1 = element1.getChildNodes(); - NodeList children2 = element2.getChildNodes(); - int nextIndex1 = 0; - int nextIndex2 = 0; - while (true) { - while (nextIndex1 < children1.getLength() && - children1.item(nextIndex1).getNodeType() != Node.ELEMENT_NODE) { - nextIndex1++; - } - - while (nextIndex2 < children2.getLength() && - children2.item(nextIndex2).getNodeType() != Node.ELEMENT_NODE) { - nextIndex2++; - } - - Element nextElement1 = (Element) (nextIndex1 < children1.getLength() - ? children1.item(nextIndex1) : null); - Element nextElement2 = (Element) (nextIndex2 < children2.getLength() - ? children2.item(nextIndex2) : null); - if (nextElement1 == null) { - return nextElement2 == null; - } else if (nextElement2 == null) { - return false; - } else if (!isEquivalent(nextElement1, nextElement2)) { - return false; - } - nextIndex1++; - nextIndex2++; - } - } - - /** - * Finds the corresponding element in a document to a given element in another - * document. Note that this does <b>not</b> do any kind of equivalence check - * (see {@link #isEquivalent(Element, Element)}), and currently the search - * is only by id; there is no structural search. - * - * @param element the element to find an equivalent for - * @param document the document to search for an equivalent element in - * @return an equivalent element, or null - */ - @Nullable - public static Element findCorresponding(@NonNull Element element, @NonNull Document document) { - // Make sure the method is called correctly -- the element is for a different - // document than the one we are searching - assert element.getOwnerDocument() != document; - - // First search by id. This allows us to find the corresponding - String id = element.getAttributeNS(ANDROID_URI, ATTR_ID); - if (id != null && id.length() > 0) { - if (id.startsWith(ID_PREFIX)) { - id = NEW_ID_PREFIX + id.substring(ID_PREFIX.length()); - } - - return findCorresponding(document.getDocumentElement(), id); - } - - // TODO: Search by structure - look in the document and - // find a corresponding element in the same location in the structure, - // e.g. 4th child of root, 3rd child, 6th child, then pick node with tag "foo". - - return null; - } - - /** Helper method for {@link #findCorresponding(Element, Document)} */ - @Nullable - private static Element findCorresponding(@NonNull Element element, @NonNull String targetId) { - String id = element.getAttributeNS(ANDROID_URI, ATTR_ID); - if (id != null) { // Work around DOM bug - if (id.equals(targetId)) { - return element; - } else if (id.startsWith(ID_PREFIX)) { - id = NEW_ID_PREFIX + id.substring(ID_PREFIX.length()); - if (id.equals(targetId)) { - return element; - } - } - } - - NodeList children = element.getChildNodes(); - for (int i = 0, n = children.getLength(); i < n; i++) { - Node node = children.item(i); - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element child = (Element) node; - Element match = findCorresponding(child, targetId); - if (match != null) { - return match; - } - } - } - - return null; - } - - /** - * Parses the given XML string as a DOM document, using Eclipse's structured - * XML model (which for example allows us to distinguish empty elements - * (<foo/>) from elements with no children (<foo></foo>). - * - * @param xml the XML content to be parsed (must be well formed) - * @return the DOM document, or null - */ - @Nullable - public static Document parseStructuredDocument(@NonNull String xml) { - IStructuredModel model = createStructuredModel(xml); - if (model instanceof IDOMModel) { - IDOMModel domModel = (IDOMModel) model; - return domModel.getDocument(); - } - - return null; - } - - /** - * Parses the given XML string and builds an Eclipse structured model for it. - * - * @param xml the XML content to be parsed (must be well formed) - * @return the structured model - */ - @Nullable - public static IStructuredModel createStructuredModel(@NonNull String xml) { - IStructuredModel model = createEmptyModel(); - IStructuredDocument document = model.getStructuredDocument(); - model.aboutToChangeModel(); - document.set(xml); - model.changedModel(); - - return model; - } - - /** - * Creates an empty Eclipse XML model - * - * @return a new Eclipse XML model - */ - @NonNull - public static IStructuredModel createEmptyModel() { - IModelManager modelManager = StructuredModelManager.getModelManager(); - return modelManager.createUnManagedStructuredModelFor(ContentTypeID_XML); - } - - /** - * Creates an empty Eclipse XML document - * - * @return an empty Eclipse XML document - */ - @Nullable - public static Document createEmptyDocument() { - IStructuredModel model = createEmptyModel(); - if (model instanceof IDOMModel) { - IDOMModel domModel = (IDOMModel) model; - return domModel.getDocument(); - } - - return null; - } - - /** - * Creates an empty non-Eclipse XML document. - * This is used when you need to use XML operations not supported by - * the Eclipse XML model (such as serialization). - * <p> - * The new document will not validate, will ignore comments, and will - * support namespace. - * - * @return the new document - */ - @Nullable - public static Document createEmptyPlainDocument() { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setValidating(false); - factory.setIgnoringComments(true); - DocumentBuilder builder; - try { - builder = factory.newDocumentBuilder(); - return builder.newDocument(); - } catch (ParserConfigurationException e) { - AdtPlugin.log(e, null); - } - - return null; - } - - /** - * Parses the given XML string as a DOM document, using the JDK parser. - * The parser does not validate, and is namespace aware. - * - * @param xml the XML content to be parsed (must be well formed) - * @param logParserErrors if true, log parser errors to the log, otherwise - * silently return null - * @return the DOM document, or null - */ - @Nullable - public static Document parseDocument(@NonNull String xml, boolean logParserErrors) { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - InputSource is = new InputSource(new StringReader(xml)); - factory.setNamespaceAware(true); - factory.setValidating(false); - try { - DocumentBuilder builder = factory.newDocumentBuilder(); - return builder.parse(is); - } catch (Exception e) { - if (logParserErrors) { - AdtPlugin.log(e, null); - } - } - - return null; - } - - /** Can be used to sort attributes by name */ - private static final Comparator<Attr> ATTRIBUTE_COMPARATOR = new Comparator<Attr>() { - @Override - public int compare(Attr a1, Attr a2) { - return a1.getName().compareTo(a2.getName()); - } - }; -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DropGesture.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DropGesture.java deleted file mode 100644 index bb3be7f68..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DropGesture.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2010 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.dnd.DropTargetEvent; -import org.eclipse.swt.dnd.DropTargetListener; - -/** - * A {@link DropGesture} is a {@link Gesture} which deals with drag and drop, so - * it has additional hooks for indicating whether the current position is - * "valid", and in general gets access to the system drag and drop data - * structures. See the {@link Gesture} documentation for more details on whether - * you should choose a plain {@link Gesture} or a {@link DropGesture}. - */ -public abstract class DropGesture extends Gesture { - /** - * The cursor has entered the drop target boundaries. - * - * @param event The {@link DropTargetEvent} for this drag and drop event - * @see DropTargetListener#dragEnter(DropTargetEvent) - */ - public void dragEnter(DropTargetEvent event) { - } - - /** - * The cursor is moving over the drop target. - * - * @param event The {@link DropTargetEvent} for this drag and drop event - * @see DropTargetListener#dragOver(DropTargetEvent) - */ - public void dragOver(DropTargetEvent event) { - } - - /** - * The operation being performed has changed (usually due to the user - * changing the selected modifier key(s) while dragging). - * - * @param event The {@link DropTargetEvent} for this drag and drop event - * @see DropTargetListener#dragOperationChanged(DropTargetEvent) - */ - public void dragOperationChanged(DropTargetEvent event) { - } - - /** - * The cursor has left the drop target boundaries OR the drop has been - * canceled OR the data is about to be dropped. - * - * @param event The {@link DropTargetEvent} for this drag and drop event - * @see DropTargetListener#dragLeave(DropTargetEvent) - */ - public void dragLeave(DropTargetEvent event) { - } - - /** - * The drop is about to be performed. The drop target is given a last chance - * to change the nature of the drop. - * - * @param event The {@link DropTargetEvent} for this drag and drop event - * @see DropTargetListener#dropAccept(DropTargetEvent) - */ - public void dropAccept(DropTargetEvent event) { - } - - /** - * The data is being dropped. The data field contains java format of the - * data being dropped. - * - * @param event The {@link DropTargetEvent} for this drag and drop event - * @see DropTargetListener#drop(DropTargetEvent) - */ - public void drop(final DropTargetEvent event) { - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java deleted file mode 100644 index fc7127278..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java +++ /dev/null @@ -1,654 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW; -import static com.android.SdkConstants.FQCN_GESTURE_OVERLAY_VIEW; -import static com.android.SdkConstants.FQCN_IMAGE_VIEW; -import static com.android.SdkConstants.FQCN_LINEAR_LAYOUT; -import static com.android.SdkConstants.FQCN_TEXT_VIEW; -import static com.android.SdkConstants.GRID_VIEW; -import static com.android.SdkConstants.LIST_VIEW; -import static com.android.SdkConstants.SPINNER; -import static com.android.SdkConstants.VIEW_FRAGMENT; - -import com.android.SdkConstants; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.RuleAction; -import com.android.ide.common.api.RuleAction.Choices; -import com.android.ide.common.api.RuleAction.NestedAction; -import com.android.ide.common.api.RuleAction.Toggle; -import com.android.ide.common.layout.BaseViewRule; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -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.refactoring.ChangeLayoutAction; -import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeViewAction; -import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractIncludeAction; -import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractStyleAction; -import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.UnwrapAction; -import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.UseCompoundDrawableAction; -import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.WrapInAction; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.ContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IContributionItem; -import org.eclipse.jface.action.IMenuListener; -import org.eclipse.jface.action.IMenuManager; -import org.eclipse.jface.action.MenuManager; -import org.eclipse.jface.action.Separator; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Menu; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Helper class that is responsible for adding and managing the dynamic menu items - * contributed by the {@link IViewRule} instances, based on the current selection - * on the {@link LayoutCanvas}. - * <p/> - * This class is tied to a specific {@link LayoutCanvas} instance and a root {@link MenuManager}. - * <p/> - * Two instances of this are used: one created by {@link LayoutCanvas} and the other one - * created by {@link OutlinePage}. Different root {@link MenuManager}s are populated, however - * they are both linked to the current selection state of the {@link LayoutCanvas}. - */ -class DynamicContextMenu { - public static String DEFAULT_ACTION_SHORTCUT = "F2"; //$NON-NLS-1$ - public static int DEFAULT_ACTION_KEY = SWT.F2; - - /** The XML layout editor that contains the canvas that uses this menu. */ - private final LayoutEditorDelegate mEditorDelegate; - - /** The layout canvas that displays this context menu. */ - private final LayoutCanvas mCanvas; - - /** The root menu manager of the context menu. */ - private final MenuManager mMenuManager; - - /** - * Creates a new helper responsible for adding and managing the dynamic menu items - * contributed by the {@link IViewRule} instances, based on the current selection - * on the {@link LayoutCanvas}. - * @param editorDelegate the editor owning the menu - * @param canvas The {@link LayoutCanvas} providing the selection, the node factory and - * the rules engine. - * @param rootMenu The root of the context menu displayed. In practice this may be the - * context menu manager of the {@link LayoutCanvas} or the one from {@link OutlinePage}. - */ - public DynamicContextMenu( - LayoutEditorDelegate editorDelegate, - LayoutCanvas canvas, - MenuManager rootMenu) { - mEditorDelegate = editorDelegate; - mCanvas = canvas; - mMenuManager = rootMenu; - - setupDynamicMenuActions(); - } - - /** - * Setups the menu manager to receive dynamic menu contributions from the {@link IViewRule}s - * when it's about to be shown. - */ - private void setupDynamicMenuActions() { - // Remember how many static actions we have. Then each time the menu is - // shown, find dynamic contributions based on the current selection and insert - // them at the beginning of the menu. - final int numStaticActions = mMenuManager.getSize(); - mMenuManager.addMenuListener(new IMenuListener() { - @Override - public void menuAboutToShow(IMenuManager manager) { - - // Remove any previous dynamic contributions to keep only the - // default static items. - int n = mMenuManager.getSize() - numStaticActions; - if (n > 0) { - IContributionItem[] items = mMenuManager.getItems(); - for (int i = 0; i < n; i++) { - mMenuManager.remove(items[i]); - } - } - - // Now add all the dynamic menu actions depending on the current selection. - populateDynamicContextMenu(); - } - }); - - } - - /** - * This method is invoked by <code>menuAboutToShow</code> on {@link #mMenuManager}. - * All previous dynamic menu actions have been removed and this method can now insert - * any new actions that depend on the current selection. - */ - private void populateDynamicContextMenu() { - // Create the actual menu contributions - String endId = mMenuManager.getItems()[0].getId(); - - Separator sep = new Separator(); - sep.setId("-dyn-gle-sep"); //$NON-NLS-1$ - mMenuManager.insertBefore(endId, sep); - endId = sep.getId(); - - List<SelectionItem> selections = mCanvas.getSelectionManager().getSelections(); - if (selections.size() == 0) { - return; - } - List<INode> nodes = new ArrayList<INode>(selections.size()); - for (SelectionItem item : selections) { - nodes.add(item.getNode()); - } - - List<IContributionItem> menuItems = getMenuItems(nodes); - for (IContributionItem menuItem : menuItems) { - mMenuManager.insertBefore(endId, menuItem); - } - - insertTagSpecificMenus(endId); - insertVisualRefactorings(endId); - insertParentItems(endId); - } - - /** - * Returns the list of node-specific actions applicable to the given - * collection of nodes - * - * @param nodes the collection of nodes to look up actions for - * @return a list of contribution items applicable for all the nodes - */ - private List<IContributionItem> getMenuItems(List<INode> nodes) { - Map<INode, List<RuleAction>> allActions = new HashMap<INode, List<RuleAction>>(); - for (INode node : nodes) { - List<RuleAction> actionList = getMenuActions((NodeProxy) node); - allActions.put(node, actionList); - } - - Set<String> availableIds = computeApplicableActionIds(allActions); - - // +10: Make room for separators too - List<IContributionItem> items = new ArrayList<IContributionItem>(availableIds.size() + 10); - - // We'll use the actions returned by the first node. Even when there - // are multiple items selected, we'll use the first action, but pass - // the set of all selected nodes to that first action. Actions are required - // to work this way to facilitate multi selection and actions which apply - // to multiple nodes. - NodeProxy first = (NodeProxy) nodes.get(0); - List<RuleAction> firstSelectedActions = allActions.get(first); - String defaultId = getDefaultActionId(first); - for (RuleAction action : firstSelectedActions) { - if (!availableIds.contains(action.getId()) - && !(action instanceof RuleAction.Separator)) { - // This action isn't supported by all selected items. - continue; - } - - items.add(createContributionItem(action, nodes, defaultId)); - } - - return items; - } - - private void insertParentItems(String endId) { - List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); - if (selection.size() == 1) { - mMenuManager.insertBefore(endId, new Separator()); - INode parent = selection.get(0).getNode().getParent(); - while (parent != null) { - String id = parent.getStringAttr(ANDROID_URI, ATTR_ID); - String label; - if (id != null && id.length() > 0) { - label = BaseViewRule.stripIdPrefix(id); - } else { - // Use the view name, such as "Button", as the label - label = parent.getFqcn(); - // Strip off package - label = label.substring(label.lastIndexOf('.') + 1); - } - mMenuManager.insertBefore(endId, new NestedParentMenu(label, parent)); - parent = parent.getParent(); - } - mMenuManager.insertBefore(endId, new Separator()); - } - } - - private void insertVisualRefactorings(String endId) { - // Extract As <include> refactoring, Wrap In Refactoring, etc. - List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); - if (selection.size() == 0) { - return; - } - // Only include the menu item if you are not right clicking on a root, - // or on an included view, or on a non-contiguous selection - mMenuManager.insertBefore(endId, new Separator()); - if (selection.size() == 1 && selection.get(0).getViewInfo() != null - && selection.get(0).getViewInfo().getName().equals(FQCN_LINEAR_LAYOUT)) { - CanvasViewInfo info = selection.get(0).getViewInfo(); - List<CanvasViewInfo> children = info.getChildren(); - if (children.size() == 2) { - String first = children.get(0).getName(); - String second = children.get(1).getName(); - if ((first.equals(FQCN_IMAGE_VIEW) && second.equals(FQCN_TEXT_VIEW)) - || (first.equals(FQCN_TEXT_VIEW) && second.equals(FQCN_IMAGE_VIEW))) { - mMenuManager.insertBefore(endId, UseCompoundDrawableAction.create( - mEditorDelegate)); - } - } - } - mMenuManager.insertBefore(endId, ExtractIncludeAction.create(mEditorDelegate)); - mMenuManager.insertBefore(endId, ExtractStyleAction.create(mEditorDelegate)); - mMenuManager.insertBefore(endId, WrapInAction.create(mEditorDelegate)); - if (selection.size() == 1 && !(selection.get(0).isRoot())) { - mMenuManager.insertBefore(endId, UnwrapAction.create(mEditorDelegate)); - } - if (selection.size() == 1 && (selection.get(0).isLayout() || - selection.get(0).getViewInfo().getName().equals(FQCN_GESTURE_OVERLAY_VIEW))) { - mMenuManager.insertBefore(endId, ChangeLayoutAction.create(mEditorDelegate)); - } else { - mMenuManager.insertBefore(endId, ChangeViewAction.create(mEditorDelegate)); - } - mMenuManager.insertBefore(endId, new Separator()); - } - - /** "Preview List Content" pull-right menu for lists, "Preview Fragment" for fragments, etc. */ - private void insertTagSpecificMenus(String endId) { - - List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); - if (selection.size() == 0) { - return; - } - for (SelectionItem item : selection) { - UiViewElementNode node = item.getViewInfo().getUiViewNode(); - String name = node.getDescriptor().getXmlLocalName(); - boolean isGrid = name.equals(GRID_VIEW); - boolean isSpinner = name.equals(SPINNER); - if (name.equals(LIST_VIEW) || name.equals(EXPANDABLE_LIST_VIEW) - || isGrid || isSpinner) { - mMenuManager.insertBefore(endId, new Separator()); - mMenuManager.insertBefore(endId, new ListViewTypeMenu(mCanvas, isGrid, isSpinner)); - return; - } else if (name.equals(VIEW_FRAGMENT) && selection.size() == 1) { - mMenuManager.insertBefore(endId, new Separator()); - mMenuManager.insertBefore(endId, new FragmentMenu(mCanvas)); - return; - } - } - } - - /** - * Given a map from selection items to list of applicable actions (produced - * by {@link #computeApplicableActions()}) this method computes the set of - * common actions and returns the action ids of these actions. - * - * @param actions a map from selection item to list of actions applicable to - * that selection item - * @return set of action ids for the actions that are present in the action - * lists for all selected items - */ - private Set<String> computeApplicableActionIds(Map<INode, List<RuleAction>> actions) { - if (actions.size() > 1) { - // More than one view is selected, so we have to filter down the available - // actions such that only those actions that are defined for all the views - // are shown - Map<String, Integer> idCounts = new HashMap<String, Integer>(); - for (Map.Entry<INode, List<RuleAction>> entry : actions.entrySet()) { - List<RuleAction> actionList = entry.getValue(); - for (RuleAction action : actionList) { - if (!action.supportsMultipleNodes()) { - continue; - } - String id = action.getId(); - if (id != null) { - assert id != null : action; - Integer count = idCounts.get(id); - if (count == null) { - idCounts.put(id, Integer.valueOf(1)); - } else { - idCounts.put(id, count + 1); - } - } - } - } - Integer selectionCount = Integer.valueOf(actions.size()); - Set<String> validIds = new HashSet<String>(idCounts.size()); - for (Map.Entry<String, Integer> entry : idCounts.entrySet()) { - Integer count = entry.getValue(); - if (selectionCount.equals(count)) { - String id = entry.getKey(); - validIds.add(id); - } - } - return validIds; - } else { - List<RuleAction> actionList = actions.values().iterator().next(); - Set<String> validIds = new HashSet<String>(actionList.size()); - for (RuleAction action : actionList) { - String id = action.getId(); - validIds.add(id); - } - return validIds; - } - } - - /** - * Returns the menu actions computed by the rule associated with this node. - * - * @param node the canvas node we need menu actions for - * @return a list of {@link RuleAction} objects applicable to the node - */ - private List<RuleAction> getMenuActions(NodeProxy node) { - List<RuleAction> actions = mCanvas.getRulesEngine().callGetContextMenu(node); - if (actions == null || actions.size() == 0) { - return null; - } - - return actions; - } - - /** - * Returns the default action id, or null - * - * @param node the node to look up the default action for - * @return the action id, or null - */ - private String getDefaultActionId(NodeProxy node) { - return mCanvas.getRulesEngine().callGetDefaultActionId(node); - } - - /** - * Creates a {@link ContributionItem} for the given {@link RuleAction}. - * - * @param action the action to create a {@link ContributionItem} for - * @param nodes the set of nodes the action should be applied to - * @param defaultId if not non null, the id of an action which should be considered default - * @return a new {@link ContributionItem} which implements the given action - * on the given nodes - */ - private ContributionItem createContributionItem(final RuleAction action, - final List<INode> nodes, final String defaultId) { - if (action instanceof RuleAction.Separator) { - return new Separator(); - } else if (action instanceof NestedAction) { - NestedAction parentAction = (NestedAction) action; - return new ActionContributionItem(new NestedActionMenu(parentAction, nodes)); - } else if (action instanceof Choices) { - Choices parentAction = (Choices) action; - return new ActionContributionItem(new NestedChoiceMenu(parentAction, nodes)); - } else if (action instanceof Toggle) { - return new ActionContributionItem(createToggleAction(action, nodes)); - } else { - return new ActionContributionItem(createPlainAction(action, nodes, defaultId)); - } - } - - private Action createToggleAction(final RuleAction action, final List<INode> nodes) { - Toggle toggleAction = (Toggle) action; - final boolean isChecked = toggleAction.isChecked(); - Action a = new Action(action.getTitle(), IAction.AS_CHECK_BOX) { - @Override - public void run() { - String label = createActionLabel(action, nodes); - mEditorDelegate.getEditor().wrapUndoEditXmlModel(label, new Runnable() { - @Override - public void run() { - action.getCallback().action(action, nodes, - null/* no valueId for a toggle */, !isChecked); - applyPendingChanges(); - } - }); - } - }; - a.setId(action.getId()); - a.setChecked(isChecked); - return a; - } - - private IAction createPlainAction(final RuleAction action, final List<INode> nodes, - final String defaultId) { - IAction a = new Action(action.getTitle(), IAction.AS_PUSH_BUTTON) { - @Override - public void run() { - String label = createActionLabel(action, nodes); - mEditorDelegate.getEditor().wrapUndoEditXmlModel(label, new Runnable() { - @Override - public void run() { - action.getCallback().action(action, nodes, null, - Boolean.TRUE); - applyPendingChanges(); - } - }); - } - }; - - String id = action.getId(); - if (defaultId != null && id.equals(defaultId)) { - a.setAccelerator(DEFAULT_ACTION_KEY); - String text = a.getText(); - text = text + '\t' + DEFAULT_ACTION_SHORTCUT; - a.setText(text); - - } else if (ATTR_ID.equals(id)) { - // Keep in sync with {@link LayoutCanvas#handleKeyPressed} - if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) { - a.setAccelerator('R' | SWT.MOD1 | SWT.MOD3); - // Option+Command - a.setText(a.getText().trim() + "\t\u2325\u2318R"); //$NON-NLS-1$ - } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) { - a.setAccelerator('R' | SWT.MOD2 | SWT.MOD3); - a.setText(a.getText() + "\tShift+Alt+R"); //$NON-NLS-1$ - } else { - a.setAccelerator('R' | SWT.MOD2 | SWT.MOD3); - a.setText(a.getText() + "\tAlt+Shift+R"); //$NON-NLS-1$ - } - } - a.setId(id); - return a; - } - - private static String createActionLabel(final RuleAction action, final List<INode> nodes) { - String label = action.getTitle(); - if (nodes.size() > 1) { - label += String.format(" (%d elements)", nodes.size()); - } - return label; - } - - /** - * The {@link NestedParentMenu} provides submenu content which adds actions - * available on one of the selected node's parent nodes. This will be - * similar to the menu content for the selected node, except the parent - * menus will not be embedded within the nested menu. - */ - private class NestedParentMenu extends SubmenuAction { - INode mParent; - - NestedParentMenu(String title, INode parent) { - super(title); - mParent = parent; - } - - @Override - protected void addMenuItems(Menu menu) { - List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); - if (selection.size() == 0) { - return; - } - - List<IContributionItem> menuItems = getMenuItems(Collections.singletonList(mParent)); - for (IContributionItem menuItem : menuItems) { - menuItem.fill(menu, -1); - } - } - } - - /** - * The {@link NestedActionMenu} creates a lazily populated pull-right menu - * where the children are {@link RuleAction}'s themselves. - */ - private class NestedActionMenu extends SubmenuAction { - private final NestedAction mParentAction; - private final List<INode> mNodes; - - NestedActionMenu(NestedAction parentAction, List<INode> nodes) { - super(parentAction.getTitle()); - mParentAction = parentAction; - mNodes = nodes; - - assert mNodes.size() > 0; - } - - @Override - protected void addMenuItems(Menu menu) { - Map<INode, List<RuleAction>> allActions = new HashMap<INode, List<RuleAction>>(); - for (INode node : mNodes) { - List<RuleAction> actionList = mParentAction.getNestedActions(node); - allActions.put(node, actionList); - } - - Set<String> availableIds = computeApplicableActionIds(allActions); - - NodeProxy first = (NodeProxy) mNodes.get(0); - String defaultId = getDefaultActionId(first); - List<RuleAction> firstSelectedActions = allActions.get(first); - - int count = 0; - for (RuleAction firstAction : firstSelectedActions) { - if (!availableIds.contains(firstAction.getId()) - && !(firstAction instanceof RuleAction.Separator)) { - // This action isn't supported by all selected items. - continue; - } - - createContributionItem(firstAction, mNodes, defaultId).fill(menu, -1); - count++; - } - - if (count == 0) { - addDisabledMessageItem("<Empty>"); - } - } - } - - private void applyPendingChanges() { - LayoutCanvas canvas = mEditorDelegate.getGraphicalEditor().getCanvasControl(); - CanvasViewInfo root = canvas.getViewHierarchy().getRoot(); - if (root != null) { - UiViewElementNode uiViewNode = root.getUiViewNode(); - NodeFactory nodeFactory = canvas.getNodeFactory(); - NodeProxy rootNode = nodeFactory.create(uiViewNode); - if (rootNode != null) { - rootNode.applyPendingChanges(); - } - } - } - - /** - * The {@link NestedChoiceMenu} creates a lazily populated pull-right menu - * where the items in the menu are strings - */ - private class NestedChoiceMenu extends SubmenuAction { - private final Choices mParentAction; - private final List<INode> mNodes; - - NestedChoiceMenu(Choices parentAction, List<INode> nodes) { - super(parentAction.getTitle()); - mParentAction = parentAction; - mNodes = nodes; - } - - @Override - protected void addMenuItems(Menu menu) { - List<String> titles = mParentAction.getTitles(); - List<String> ids = mParentAction.getIds(); - String current = mParentAction.getCurrent(); - assert titles.size() == ids.size(); - String[] currentValues = current != null - && current.indexOf(RuleAction.CHOICE_SEP) != -1 ? - current.split(RuleAction.CHOICE_SEP_PATTERN) : null; - for (int i = 0, n = Math.min(titles.size(), ids.size()); i < n; i++) { - final String id = ids.get(i); - if (id == null || id.equals(RuleAction.SEPARATOR)) { - new Separator().fill(menu, -1); - continue; - } - - // Find out whether this item is selected - boolean select = false; - if (current != null) { - // The current choice has a separator, so it's a flag with - // multiple values selected. Compare keys with the split - // values. - if (currentValues != null) { - if (current.indexOf(id) >= 0) { - for (String value : currentValues) { - if (id.equals(value)) { - select = true; - break; - } - } - } - } else { - // current choice has no separator, simply compare to the key - select = id.equals(current); - } - } - - String title = titles.get(i); - IAction a = new Action(title, - current != null ? IAction.AS_CHECK_BOX : IAction.AS_PUSH_BUTTON) { - @Override - public void runWithEvent(Event event) { - run(); - } - @Override - public void run() { - String label = createActionLabel(mParentAction, mNodes); - mEditorDelegate.getEditor().wrapUndoEditXmlModel(label, new Runnable() { - @Override - public void run() { - mParentAction.getCallback().action(mParentAction, mNodes, id, - Boolean.TRUE); - applyPendingChanges(); - } - }); - } - }; - a.setId(id); - a.setEnabled(true); - if (select) { - a.setChecked(true); - } - - new ActionContributionItem(a).fill(menu, -1); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java deleted file mode 100644 index daa3e0eae..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/EmptyViewsOverlay.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2010 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.graphics.Color; -import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Rectangle; - -/** - * The {@link EmptyViewsOverlay} paints bounding rectangles for any of the empty and - * invisible container views in the scene. - */ -public class EmptyViewsOverlay extends Overlay { - /** The {@link ViewHierarchy} containing visible view information. */ - private final ViewHierarchy mViewHierarchy; - - /** Border color to paint the bounding boxes with. */ - private Color mBorderColor; - - /** Vertical scaling & scrollbar information. */ - private CanvasTransform mVScale; - - /** Horizontal scaling & scrollbar information. */ - private CanvasTransform mHScale; - - /** - * Constructs a new {@link EmptyViewsOverlay} linked to the given view hierarchy. - * - * @param viewHierarchy The {@link ViewHierarchy} to render. - * @param hScale The {@link CanvasTransform} to use to transfer horizontal layout - * coordinates to screen coordinates. - * @param vScale The {@link CanvasTransform} to use to transfer vertical layout coordinates - * to screen coordinates. - */ - public EmptyViewsOverlay( - ViewHierarchy viewHierarchy, - CanvasTransform hScale, - CanvasTransform vScale) { - super(); - mViewHierarchy = viewHierarchy; - mHScale = hScale; - mVScale = vScale; - } - - @Override - public void create(Device device) { - mBorderColor = new Color(device, SwtDrawingStyle.EMPTY.getStrokeColor()); - } - - @Override - public void dispose() { - if (mBorderColor != null) { - mBorderColor.dispose(); - mBorderColor = null; - } - } - - @Override - public void paint(GC gc) { - gc.setForeground(mBorderColor); - gc.setLineDash(null); - gc.setLineStyle(SwtDrawingStyle.EMPTY.getLineStyle()); - int oldAlpha = gc.getAlpha(); - gc.setAlpha(SwtDrawingStyle.EMPTY.getStrokeAlpha()); - gc.setLineWidth(SwtDrawingStyle.EMPTY.getLineWidth()); - - for (CanvasViewInfo info : mViewHierarchy.getInvisibleViews()) { - Rectangle r = info.getAbsRect(); - - int x = mHScale.translate(r.x); - int y = mVScale.translate(r.y); - int w = mHScale.scale(r.width); - int h = mVScale.scale(r.height); - - // +1: See explanation in equivalent code in {@link OutlineOverlay#paint} - gc.drawRectangle(x, y, w + 1, h + 1); - } - - gc.setAlpha(oldAlpha); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExportScreenshotAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExportScreenshotAction.java deleted file mode 100644 index ac3328db2..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExportScreenshotAction.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.DOT_PNG; - -import com.android.ide.eclipse.adt.AdtPlugin; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Shell; - -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; - -import javax.imageio.ImageIO; - -/** Saves the current layout editor's rendered image to disk */ -class ExportScreenshotAction extends Action { - private final LayoutCanvas mCanvas; - - ExportScreenshotAction(LayoutCanvas canvas) { - super("Export Screenshot..."); - mCanvas = canvas; - } - - @Override - public void run() { - Shell shell = AdtPlugin.getShell(); - - ImageOverlay imageOverlay = mCanvas.getImageOverlay(); - BufferedImage image = imageOverlay.getAwtImage(); - if (image != null) { - FileDialog dialog = new FileDialog(shell, SWT.SAVE); - dialog.setFilterExtensions(new String[] { "*.png" }); //$NON-NLS-1$ - String path = dialog.open(); - if (path != null) { - if (!path.endsWith(DOT_PNG)) { - path = path + DOT_PNG; - } - File file = new File(path); - if (file.exists()) { - MessageDialog d = new MessageDialog(null, "File Already Exists", null, - String.format( - "%1$s already exists.\nWould you like to replace it?", - path), - MessageDialog.QUESTION, new String[] { - // Yes will be moved to the end because it's the default - "Yes", "No" - }, 0); - int result = d.open(); - if (result != 0) { - return; - } - } - try { - ImageIO.write(image, "PNG", file); //$NON-NLS-1$ - } catch (IOException e) { - AdtPlugin.log(e, null); - } - } - } else { - MessageDialog.openError(shell, "Error", "Image not available"); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/FragmentMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/FragmentMenu.java deleted file mode 100644 index f7085fc12..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/FragmentMenu.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * 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.SdkConstants.ANDROID_LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_CLASS; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_FRAGMENT_LAYOUT; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator; -import com.android.ide.eclipse.adt.internal.ui.ResourceChooser; -import com.android.resources.ResourceType; -import com.android.utils.Pair; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IType; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.Separator; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.widgets.Menu; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import java.util.ArrayList; -import java.util.List; - -/** - * Fragment context menu allowing a layout to be chosen for previewing in the fragment frame. - */ -public class FragmentMenu extends SubmenuAction { - private static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$ - private static final String ANDROID_R_PREFIX = "android.R.layout"; //$NON-NLS-1$ - - /** Associated canvas */ - private final LayoutCanvas mCanvas; - - /** - * Creates a "Preview Fragment" menu - * - * @param canvas associated canvas - */ - public FragmentMenu(LayoutCanvas canvas) { - super("Fragment Layout"); - mCanvas = canvas; - } - - @Override - protected void addMenuItems(Menu menu) { - IAction action = new PickLayoutAction("Choose Layout..."); - new ActionContributionItem(action).fill(menu, -1); - - SelectionManager selectionManager = mCanvas.getSelectionManager(); - List<SelectionItem> selections = selectionManager.getSelections(); - if (selections.size() == 0) { - return; - } - - SelectionItem first = selections.get(0); - UiViewElementNode node = first.getViewInfo().getUiViewNode(); - if (node == null) { - return; - } - Element element = (Element) node.getXmlNode(); - - String selected = getSelectedLayout(); - if (selected != null) { - if (selected.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX)) { - selected = selected.substring(ANDROID_LAYOUT_RESOURCE_PREFIX.length()); - } - } - - String fqcn = getFragmentClass(element); - if (fqcn != null) { - // Look up the corresponding activity class and try to figure out - // which layouts it is referring to and list these here as reasonable - // guesses - IProject project = mCanvas.getEditorDelegate().getEditor().getProject(); - String source = null; - try { - IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); - IType type = javaProject.findType(fqcn); - if (type != null) { - source = type.getSource(); - } - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - // Find layouts. This is based on just skimming the Fragment class and looking - // for layout references of the form R.layout.*. - if (source != null) { - String self = mCanvas.getLayoutResourceName(); - // Pair of <title,layout> to be displayed to the user - List<Pair<String, String>> layouts = new ArrayList<Pair<String, String>>(); - - if (source.contains("extends ListFragment")) { //$NON-NLS-1$ - layouts.add(Pair.of("list_content", //$NON-NLS-1$ - "@android:layout/list_content")); //$NON-NLS-1$ - } - - int index = 0; - while (true) { - index = source.indexOf(R_LAYOUT_RESOURCE_PREFIX, index); - if (index == -1) { - break; - } else { - index += R_LAYOUT_RESOURCE_PREFIX.length(); - int end = index; - while (end < source.length()) { - char c = source.charAt(end); - if (!Character.isJavaIdentifierPart(c)) { - break; - } - end++; - } - if (end > index) { - String title = source.substring(index, end); - String layout; - // Is this R.layout part of an android.R.layout? - int len = ANDROID_R_PREFIX.length() + 1; // prefix length to check - if (index > len && source.startsWith(ANDROID_R_PREFIX, index - len)) { - layout = ANDROID_LAYOUT_RESOURCE_PREFIX + title; - } else { - layout = LAYOUT_RESOURCE_PREFIX + title; - } - if (!self.equals(title)) { - layouts.add(Pair.of(title, layout)); - } - } - } - - index++; - } - - if (layouts.size() > 0) { - new Separator().fill(menu, -1); - for (Pair<String, String> layout : layouts) { - action = new SetFragmentLayoutAction(layout.getFirst(), - layout.getSecond(), selected); - new ActionContributionItem(action).fill(menu, -1); - } - } - } - } - - if (selected != null) { - new Separator().fill(menu, -1); - action = new SetFragmentLayoutAction("Clear", null, null); - new ActionContributionItem(action).fill(menu, -1); - } - } - - /** - * Returns the class name of the fragment associated with the given {@code <fragment>} - * element. - * - * @param element the element for the fragment tag - * @return the fully qualified fragment class name, or null - */ - @Nullable - public static String getFragmentClass(@NonNull Element element) { - String fqcn = element.getAttribute(ATTR_CLASS); - if (fqcn == null || fqcn.length() == 0) { - fqcn = element.getAttributeNS(ANDROID_URI, ATTR_NAME); - } - if (fqcn != null && fqcn.length() > 0) { - return fqcn; - } else { - return null; - } - } - - /** - * Returns the layout to be shown for the given {@code <fragment>} node. - * - * @param node the node corresponding to the {@code <fragment>} element - * @return the resource path to a layout to render for this fragment, or null - */ - @Nullable - public static String getFragmentLayout(@NonNull Node node) { - String layout = LayoutMetadata.getProperty( - node, LayoutMetadata.KEY_FRAGMENT_LAYOUT); - if (layout != null) { - return layout; - } - - return null; - } - - /** Returns the name of the currently displayed layout in the fragment, or null */ - @Nullable - private String getSelectedLayout() { - SelectionManager selectionManager = mCanvas.getSelectionManager(); - for (SelectionItem item : selectionManager.getSelections()) { - UiViewElementNode node = item.getViewInfo().getUiViewNode(); - if (node != null) { - String layout = getFragmentLayout(node.getXmlNode()); - if (layout != null) { - return layout; - } - } - } - return null; - } - - /** - * Set the given layout as the new fragment layout - * - * @param layout the layout resource name to show in this fragment - */ - public void setNewLayout(@Nullable String layout) { - LayoutEditorDelegate delegate = mCanvas.getEditorDelegate(); - GraphicalEditorPart graphicalEditor = delegate.getGraphicalEditor(); - SelectionManager selectionManager = mCanvas.getSelectionManager(); - - for (SelectionItem item : selectionManager.getSnapshot()) { - UiViewElementNode node = item.getViewInfo().getUiViewNode(); - if (node != null) { - Node xmlNode = node.getXmlNode(); - LayoutMetadata.setProperty(delegate.getEditor(), xmlNode, KEY_FRAGMENT_LAYOUT, - layout); - } - } - - // Refresh - graphicalEditor.recomputeLayout(); - mCanvas.redraw(); - } - - /** Action to set the given layout as the new layout in a fragment */ - private class SetFragmentLayoutAction extends Action { - private final String mLayout; - - public SetFragmentLayoutAction(String title, String layout, String selected) { - super(title, IAction.AS_RADIO_BUTTON); - mLayout = layout; - - if (layout != null && layout.equals(selected)) { - setChecked(true); - } - } - - @Override - public void run() { - if (isChecked()) { - setNewLayout(mLayout); - } - } - } - - /** - * Action which brings up the "Create new XML File" wizard, pre-selected with the - * animation category - */ - private class PickLayoutAction extends Action { - - public PickLayoutAction(String title) { - super(title, IAction.AS_PUSH_BUTTON); - } - - @Override - public void run() { - LayoutEditorDelegate delegate = mCanvas.getEditorDelegate(); - IFile file = delegate.getEditor().getInputFile(); - GraphicalEditorPart editor = delegate.getGraphicalEditor(); - ResourceChooser dlg = ResourceChooser.create(editor, ResourceType.LAYOUT) - .setInputValidator(CyclicDependencyValidator.create(file)) - .setInitialSize(85, 10) - .setCurrentResource(getSelectedLayout()); - int result = dlg.open(); - if (result == ResourceChooser.CLEAR_RETURN_CODE) { - setNewLayout(null); - } else if (result == Window.OK) { - String newType = dlg.getCurrentResource(); - setNewLayout(newType); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GCWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GCWrapper.java deleted file mode 100644 index 354517e76..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GCWrapper.java +++ /dev/null @@ -1,645 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.DrawingStyle; -import com.android.ide.common.api.IColor; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.Point; -import com.android.ide.common.api.Rect; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.SWTException; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.FontMetrics; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.RGB; - -import java.util.EnumMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Wraps an SWT {@link GC} into an {@link IGraphics} interface so that {@link IViewRule} objects - * can directly draw on the canvas. - * <p/> - * The actual wrapped GC object is only non-null during the context of a paint operation. - */ -public class GCWrapper implements IGraphics { - - /** - * The actual SWT {@link GC} being wrapped. This can change during the lifetime of the - * object. It is generally set to something during an onPaint method and then changed - * to null when not in the context of a paint. - */ - private GC mGc; - - /** - * Current style being used for drawing. - */ - private SwtDrawingStyle mCurrentStyle = SwtDrawingStyle.INVALID; - - /** - * Implementation of IColor wrapping an SWT color. - */ - private static class ColorWrapper implements IColor { - private final Color mColor; - - public ColorWrapper(Color color) { - mColor = color; - } - - public Color getColor() { - return mColor; - } - } - - /** A map of registered colors. All these colors must be disposed at the end. */ - private final HashMap<Integer, ColorWrapper> mColorMap = new HashMap<Integer, ColorWrapper>(); - - /** - * A map of the {@link SwtDrawingStyle} stroke colors that we have actually - * used (to be disposed) - */ - private final Map<DrawingStyle, Color> mStyleStrokeMap = new EnumMap<DrawingStyle, Color>( - DrawingStyle.class); - - /** - * A map of the {@link SwtDrawingStyle} fill colors that we have actually - * used (to be disposed) - */ - private final Map<DrawingStyle, Color> mStyleFillMap = new EnumMap<DrawingStyle, Color>( - DrawingStyle.class); - - /** The cached pixel height of the default current font. */ - private int mFontHeight = 0; - - /** The scaling of the canvas in X. */ - private final CanvasTransform mHScale; - /** The scaling of the canvas in Y. */ - private final CanvasTransform mVScale; - - public GCWrapper(CanvasTransform hScale, CanvasTransform vScale) { - mHScale = hScale; - mVScale = vScale; - mGc = null; - } - - void setGC(GC gc) { - mGc = gc; - } - - private GC getGc() { - return mGc; - } - - void checkGC() { - if (mGc == null) { - throw new RuntimeException("IGraphics used without a valid context."); - } - } - - void dispose() { - for (ColorWrapper c : mColorMap.values()) { - c.getColor().dispose(); - } - mColorMap.clear(); - - for (Color c : mStyleStrokeMap.values()) { - c.dispose(); - } - mStyleStrokeMap.clear(); - - for (Color c : mStyleFillMap.values()) { - c.dispose(); - } - mStyleFillMap.clear(); - } - - //------------- - - @Override - public @NonNull IColor registerColor(int rgb) { - checkGC(); - - Integer key = Integer.valueOf(rgb); - ColorWrapper c = mColorMap.get(key); - if (c == null) { - c = new ColorWrapper(new Color(getGc().getDevice(), - (rgb >> 16) & 0xFF, - (rgb >> 8) & 0xFF, - (rgb >> 0) & 0xFF)); - mColorMap.put(key, c); - } - - return c; - } - - /** Returns the (cached) pixel height of the current font. */ - @Override - public int getFontHeight() { - if (mFontHeight < 1) { - checkGC(); - FontMetrics fm = getGc().getFontMetrics(); - mFontHeight = fm.getHeight(); - } - return mFontHeight; - } - - @Override - public @NonNull IColor getForeground() { - Color c = getGc().getForeground(); - return new ColorWrapper(c); - } - - @Override - public @NonNull IColor getBackground() { - Color c = getGc().getBackground(); - return new ColorWrapper(c); - } - - @Override - public int getAlpha() { - return getGc().getAlpha(); - } - - @Override - public void setForeground(@NonNull IColor color) { - checkGC(); - getGc().setForeground(((ColorWrapper) color).getColor()); - } - - @Override - public void setBackground(@NonNull IColor color) { - checkGC(); - getGc().setBackground(((ColorWrapper) color).getColor()); - } - - @Override - public void setAlpha(int alpha) { - checkGC(); - try { - getGc().setAlpha(alpha); - } catch (SWTException e) { - // This means that we cannot set the alpha on this platform; this is - // an acceptable no-op. - } - } - - @Override - public void setLineStyle(@NonNull LineStyle style) { - int swtStyle = 0; - switch (style) { - case LINE_SOLID: - swtStyle = SWT.LINE_SOLID; - break; - case LINE_DASH: - swtStyle = SWT.LINE_DASH; - break; - case LINE_DOT: - swtStyle = SWT.LINE_DOT; - break; - case LINE_DASHDOT: - swtStyle = SWT.LINE_DASHDOT; - break; - case LINE_DASHDOTDOT: - swtStyle = SWT.LINE_DASHDOTDOT; - break; - default: - assert false : style; - break; - } - - if (swtStyle != 0) { - checkGC(); - getGc().setLineStyle(swtStyle); - } - } - - @Override - public void setLineWidth(int width) { - checkGC(); - if (width > 0) { - getGc().setLineWidth(width); - } - } - - // lines - - @Override - public void drawLine(int x1, int y1, int x2, int y2) { - checkGC(); - useStrokeAlpha(); - x1 = mHScale.translate(x1); - y1 = mVScale.translate(y1); - x2 = mHScale.translate(x2); - y2 = mVScale.translate(y2); - getGc().drawLine(x1, y1, x2, y2); - } - - @Override - public void drawLine(@NonNull Point p1, @NonNull Point p2) { - drawLine(p1.x, p1.y, p2.x, p2.y); - } - - // rectangles - - @Override - public void drawRect(int x1, int y1, int x2, int y2) { - checkGC(); - useStrokeAlpha(); - int x = mHScale.translate(x1); - int y = mVScale.translate(y1); - int w = mHScale.scale(x2 - x1); - int h = mVScale.scale(y2 - y1); - getGc().drawRectangle(x, y, w, h); - } - - @Override - public void drawRect(@NonNull Point p1, @NonNull Point p2) { - drawRect(p1.x, p1.y, p2.x, p2.y); - } - - @Override - public void drawRect(@NonNull Rect r) { - checkGC(); - useStrokeAlpha(); - int x = mHScale.translate(r.x); - int y = mVScale.translate(r.y); - int w = mHScale.scale(r.w); - int h = mVScale.scale(r.h); - getGc().drawRectangle(x, y, w, h); - } - - @Override - public void fillRect(int x1, int y1, int x2, int y2) { - checkGC(); - useFillAlpha(); - int x = mHScale.translate(x1); - int y = mVScale.translate(y1); - int w = mHScale.scale(x2 - x1); - int h = mVScale.scale(y2 - y1); - getGc().fillRectangle(x, y, w, h); - } - - @Override - public void fillRect(@NonNull Point p1, @NonNull Point p2) { - fillRect(p1.x, p1.y, p2.x, p2.y); - } - - @Override - public void fillRect(@NonNull Rect r) { - checkGC(); - useFillAlpha(); - int x = mHScale.translate(r.x); - int y = mVScale.translate(r.y); - int w = mHScale.scale(r.w); - int h = mVScale.scale(r.h); - getGc().fillRectangle(x, y, w, h); - } - - // circles (actually ovals) - - public void drawOval(int x1, int y1, int x2, int y2) { - checkGC(); - useStrokeAlpha(); - int x = mHScale.translate(x1); - int y = mVScale.translate(y1); - int w = mHScale.scale(x2 - x1); - int h = mVScale.scale(y2 - y1); - getGc().drawOval(x, y, w, h); - } - - public void drawOval(Point p1, Point p2) { - drawOval(p1.x, p1.y, p2.x, p2.y); - } - - public void drawOval(Rect r) { - checkGC(); - useStrokeAlpha(); - int x = mHScale.translate(r.x); - int y = mVScale.translate(r.y); - int w = mHScale.scale(r.w); - int h = mVScale.scale(r.h); - getGc().drawOval(x, y, w, h); - } - - public void fillOval(int x1, int y1, int x2, int y2) { - checkGC(); - useFillAlpha(); - int x = mHScale.translate(x1); - int y = mVScale.translate(y1); - int w = mHScale.scale(x2 - x1); - int h = mVScale.scale(y2 - y1); - getGc().fillOval(x, y, w, h); - } - - public void fillOval(Point p1, Point p2) { - fillOval(p1.x, p1.y, p2.x, p2.y); - } - - public void fillOval(Rect r) { - checkGC(); - useFillAlpha(); - int x = mHScale.translate(r.x); - int y = mVScale.translate(r.y); - int w = mHScale.scale(r.w); - int h = mVScale.scale(r.h); - getGc().fillOval(x, y, w, h); - } - - - // strings - - @Override - public void drawString(@NonNull String string, int x, int y) { - checkGC(); - useStrokeAlpha(); - x = mHScale.translate(x); - y = mVScale.translate(y); - // Background fill of text is not useful because it does not - // use the alpha; we instead supply a separate method (drawBoxedStrings) which - // first paints a semi-transparent mask for the text to sit on - // top of (this ensures that the text is readable regardless of - // colors of the pixels below the text) - getGc().drawString(string, x, y, true /*isTransparent*/); - } - - @Override - public void drawBoxedStrings(int x, int y, @NonNull List<?> strings) { - checkGC(); - - x = mHScale.translate(x); - y = mVScale.translate(y); - - // Compute bounds of the box by adding up the sum of the text heights - // and the max of the text widths - int width = 0; - int height = 0; - int lineHeight = getGc().getFontMetrics().getHeight(); - for (Object s : strings) { - org.eclipse.swt.graphics.Point extent = getGc().stringExtent(s.toString()); - height += extent.y; - width = Math.max(width, extent.x); - } - - // Paint a box below the text - int padding = 2; - useFillAlpha(); - getGc().fillRectangle(x - padding, y - padding, width + 2 * padding, height + 2 * padding); - - // Finally draw strings on top - useStrokeAlpha(); - int lineY = y; - for (Object s : strings) { - getGc().drawString(s.toString(), x, lineY, true /* isTransparent */); - lineY += lineHeight; - } - } - - @Override - public void drawString(@NonNull String string, @NonNull Point topLeft) { - drawString(string, topLeft.x, topLeft.y); - } - - // Styles - - @Override - public void useStyle(@NonNull DrawingStyle style) { - checkGC(); - - // Look up the specific SWT style which defines the actual - // colors and attributes to be used for the logical drawing style. - SwtDrawingStyle swtStyle = SwtDrawingStyle.of(style); - RGB stroke = swtStyle.getStrokeColor(); - if (stroke != null) { - Color color = getStrokeColor(style, stroke); - mGc.setForeground(color); - } - RGB fill = swtStyle.getFillColor(); - if (fill != null) { - Color color = getFillColor(style, fill); - mGc.setBackground(color); - } - mGc.setLineWidth(swtStyle.getLineWidth()); - mGc.setLineStyle(swtStyle.getLineStyle()); - if (swtStyle.getLineStyle() == SWT.LINE_CUSTOM) { - mGc.setLineDash(new int[] { - 8, 4 - }); - } - mCurrentStyle = swtStyle; - } - - /** Uses the stroke alpha for subsequent drawing operations. */ - private void useStrokeAlpha() { - mGc.setAlpha(mCurrentStyle.getStrokeAlpha()); - } - - /** Uses the fill alpha for subsequent drawing operations. */ - private void useFillAlpha() { - mGc.setAlpha(mCurrentStyle.getFillAlpha()); - } - - /** - * Get the SWT stroke color (foreground/border) to use for the given style, - * using the provided color description if we haven't seen this color yet. - * The color will also be placed in the {@link #mStyleStrokeMap} such that - * it can be disposed of at cleanup time. - * - * @param style The drawing style for which we want a color - * @param defaultColorDesc The RGB values to initialize the color to if we - * haven't seen this color before - * @return The color object - */ - private Color getStrokeColor(DrawingStyle style, RGB defaultColorDesc) { - return getStyleColor(style, defaultColorDesc, mStyleStrokeMap); - } - - /** - * Get the SWT fill (background/interior) color to use for the given style, - * using the provided color description if we haven't seen this color yet. - * The color will also be placed in the {@link #mStyleStrokeMap} such that - * it can be disposed of at cleanup time. - * - * @param style The drawing style for which we want a color - * @param defaultColorDesc The RGB values to initialize the color to if we - * haven't seen this color before - * @return The color object - */ - private Color getFillColor(DrawingStyle style, RGB defaultColorDesc) { - return getStyleColor(style, defaultColorDesc, mStyleFillMap); - } - - /** - * Get the SWT color to use for the given style, using the provided color - * description if we haven't seen this color yet. The color will also be - * placed in the map referenced by the map parameter such that it can be - * disposed of at cleanup time. - * - * @param style The drawing style for which we want a color - * @param defaultColorDesc The RGB values to initialize the color to if we - * haven't seen this color before - * @param map The color map to use - * @return The color object - */ - private Color getStyleColor(DrawingStyle style, RGB defaultColorDesc, - Map<DrawingStyle, Color> map) { - Color color = map.get(style); - if (color == null) { - color = new Color(getGc().getDevice(), defaultColorDesc); - map.put(style, color); - } - - return color; - } - - // dots - - @Override - public void drawPoint(int x, int y) { - checkGC(); - useStrokeAlpha(); - x = mHScale.translate(x); - y = mVScale.translate(y); - - getGc().drawPoint(x, y); - } - - // arrows - - private static final int MIN_LENGTH = 10; - - - @Override - public void drawArrow(int x1, int y1, int x2, int y2, int size) { - int arrowWidth = size; - int arrowHeight = size; - - checkGC(); - useStrokeAlpha(); - x1 = mHScale.translate(x1); - y1 = mVScale.translate(y1); - x2 = mHScale.translate(x2); - y2 = mVScale.translate(y2); - GC graphics = getGc(); - - // Make size adjustments to ensure that the arrow has enough width to be visible - if (x1 == x2 && Math.abs(y1 - y2) < MIN_LENGTH) { - int delta = (MIN_LENGTH - Math.abs(y1 - y2)) / 2; - if (y1 < y2) { - y1 -= delta; - y2 += delta; - } else { - y1 += delta; - y2-= delta; - } - - } else if (y1 == y2 && Math.abs(x1 - x2) < MIN_LENGTH) { - int delta = (MIN_LENGTH - Math.abs(x1 - x2)) / 2; - if (x1 < x2) { - x1 -= delta; - x2 += delta; - } else { - x1 += delta; - x2-= delta; - } - } - - graphics.drawLine(x1, y1, x2, y2); - - // Arrowhead: - - if (x1 == x2) { - // Vertical - if (y2 > y1) { - graphics.drawLine(x2 - arrowWidth, y2 - arrowHeight, x2, y2); - graphics.drawLine(x2 + arrowWidth, y2 - arrowHeight, x2, y2); - } else { - graphics.drawLine(x2 - arrowWidth, y2 + arrowHeight, x2, y2); - graphics.drawLine(x2 + arrowWidth, y2 + arrowHeight, x2, y2); - } - } else if (y1 == y2) { - // Horizontal - if (x2 > x1) { - graphics.drawLine(x2 - arrowHeight, y2 - arrowWidth, x2, y2); - graphics.drawLine(x2 - arrowHeight, y2 + arrowWidth, x2, y2); - } else { - graphics.drawLine(x2 + arrowHeight, y2 - arrowWidth, x2, y2); - graphics.drawLine(x2 + arrowHeight, y2 + arrowWidth, x2, y2); - } - } else { - // Compute angle: - int dy = y2 - y1; - int dx = x2 - x1; - double angle = Math.atan2(dy, dx); - double lineLength = Math.sqrt(dy * dy + dx * dx); - - // Imagine a line of the same length as the arrow, but with angle 0. - // Its two arrow lines are at (-arrowWidth, -arrowHeight) relative - // to the endpoint (x1 + lineLength, y1) stretching up to (x2,y2). - // We compute the positions of (ax,ay) for the point above and - // below this line and paint the lines to it: - double ax = x1 + lineLength - arrowHeight; - double ay = y1 - arrowWidth; - int rx = (int) (Math.cos(angle) * (ax-x1) - Math.sin(angle) * (ay-y1) + x1); - int ry = (int) (Math.sin(angle) * (ax-x1) + Math.cos(angle) * (ay-y1) + y1); - graphics.drawLine(x2, y2, rx, ry); - - ay = y1 + arrowWidth; - rx = (int) (Math.cos(angle) * (ax-x1) - Math.sin(angle) * (ay-y1) + x1); - ry = (int) (Math.sin(angle) * (ax-x1) + Math.cos(angle) * (ay-y1) + y1); - graphics.drawLine(x2, y2, rx, ry); - } - - /* TODO: Experiment with filled arrow heads? - if (x1 == x2) { - // Vertical - if (y2 > y1) { - for (int i = 0; i < arrowWidth; i++) { - graphics.drawLine(x2 - arrowWidth + i, y2 - arrowWidth + i, - x2 + arrowWidth - i, y2 - arrowWidth + i); - } - } else { - for (int i = 0; i < arrowWidth; i++) { - graphics.drawLine(x2 - arrowWidth + i, y2 + arrowWidth - i, - x2 + arrowWidth - i, y2 + arrowWidth - i); - } - } - } else if (y1 == y2) { - // Horizontal - if (x2 > x1) { - for (int i = 0; i < arrowHeight; i++) { - graphics.drawLine(x2 - arrowHeight + i, y2 - arrowHeight + i, x2 - - arrowHeight + i, y2 + arrowHeight - i); - } - } else { - for (int i = 0; i < arrowHeight; i++) { - graphics.drawLine(x2 + arrowHeight - i, y2 - arrowHeight + i, x2 - + arrowHeight - i, y2 + arrowHeight - i); - } - } - } else { - // Arbitrary angle -- need to use trig - // TODO: Implement this - } - */ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/Gesture.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/Gesture.java deleted file mode 100644 index a35d19078..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/Gesture.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2010 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.utils.Pair; - -import org.eclipse.swt.events.KeyEvent; - -import java.util.Collections; -import java.util.List; - -/** - * A gesture is a mouse or keyboard driven user operation, such as a - * swipe-select or a resize. It can be thought of as a session, since it is - * initiated, updated during user manipulation, and finally completed or - * canceled. A gesture is associated with a single undo transaction (although - * some gestures don't actually edit anything, such as a selection), and a - * gesture can have a number of graphics {@link Overlay}s which are added and - * cleaned up on behalf of the gesture by the system. - * <p/> - * Gestures are typically mouse oriented. If a mouse wishes to integrate - * with the native drag & drop support, it should also implement - * the {@link DropGesture} interface, which is a sub interface of this - * {@link Gesture} interface. There are pros and cons to using native drag - * & drop, so various gestures will differ in whether they use it. - * In particular, you should use drag & drop if your gesture should: - * <ul> - * <li> Show a native drag & drop cursor - * <li> Copy or move data, especially if this applies outside the canvas - * control window or even the application itself - * </ul> - * You might want to avoid using native drag & drop if your gesture should: - * <ul> - * <li> Continue updating itself even when the mouse cursor leaves the - * canvas window (in a drag & gesture, as soon as you leave the canvas - * the drag source is no longer informed of mouse updates, whereas a regular - * mouse listener is) - * <li> Respond to modifier keys (for example, if toggling the Shift key - * should constrain motion as is common during resizing, and so on) - * <li> Use no special cursor (for example, during a marquee selection gesture we - * don't want a native drag & drop cursor) - * </ul> - * <p/> - * Examples of gestures: - * <ul> - * <li>Move (dragging to reorder or change hierarchy of views or change visual - * layout attributes) - * <li>Marquee (swiping out a rectangle to make a selection) - * <li>Resize (dragging some edge or corner of a widget to change its size, for - * example to some new fixed size, or to "attach" it to some other edge.) - * <li>Inline Editing (editing the text of some text-oriented widget like a - * label or a button) - * <li>Link (associate two or more widgets in some way, such as an - * "is required" widget linked to a text field) - * </ul> - */ -public abstract class Gesture { - /** Start mouse coordinate, in control coordinates. */ - protected ControlPoint mStart; - - /** Initial SWT mask when the gesture started. */ - protected int mStartMask; - - /** - * Returns a list of overlays, from bottom to top (where the later overlays - * are painted on top of earlier ones if they overlap). - * - * @return A list of overlays to paint for this gesture, if applicable. - * Should not be null, but can be empty. - */ - public List<Overlay> createOverlays() { - return Collections.emptyList(); - } - - /** - * Handles initialization of this gesture. Called when the gesture is - * starting. - * - * @param pos The most recent mouse coordinate applicable to this - * gesture, relative to the canvas control. - * @param startMask The initial SWT mask for the gesture, if known, or - * otherwise 0. - */ - public void begin(ControlPoint pos, int startMask) { - mStart = pos; - mStartMask = startMask; - } - - /** - * Handles updating of the gesture state for a new mouse position. - * - * @param pos The most recent mouse coordinate applicable to this - * gesture, relative to the canvas control. - */ - public void update(ControlPoint pos) { - } - - /** - * Handles termination of the gesture. This method is called when the - * gesture has terminated (either through successful completion, or because - * it was canceled). - * - * @param pos The most recent mouse coordinate applicable to this - * gesture, relative to the canvas control. - * @param canceled True if the gesture was canceled, and false otherwise. - */ - public void end(ControlPoint pos, boolean canceled) { - } - - /** - * Handles a key press during the gesture. May be called repeatedly when the - * user is holding the key for several seconds. - * - * @param event The SWT event for the key press, - * @return true if this gesture consumed the key press, otherwise return false - */ - public boolean keyPressed(KeyEvent event) { - return false; - } - - /** - * Handles a key release during the gesture. - * - * @param event The SWT event for the key release, - * @return true if this gesture consumed the key press, otherwise return false - */ - public boolean keyReleased(KeyEvent event) { - return false; - } - - /** - * Returns whether tooltips should be display below and to the right of the mouse - * cursor. - * - * @return a pair of booleans, the first indicating whether the tooltip should be - * below and the second indicating whether the tooltip should be displayed to - * the right of the mouse cursor. - */ - public Pair<Boolean, Boolean> getTooltipPosition() { - return Pair.of(true, true); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java deleted file mode 100644 index 98bc25e37..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java +++ /dev/null @@ -1,930 +0,0 @@ -/* - * Copyright (C) 2010 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.SdkConstants; -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.SegmentType; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; -import com.android.utils.Pair; - -import org.eclipse.jface.action.IStatusLineManager; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DragSource; -import org.eclipse.swt.dnd.DragSourceEvent; -import org.eclipse.swt.dnd.DragSourceListener; -import org.eclipse.swt.dnd.DropTarget; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.dnd.DropTargetListener; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.MouseMoveListener; -import org.eclipse.swt.events.MouseTrackListener; -import org.eclipse.swt.events.TypedEvent; -import org.eclipse.swt.graphics.Cursor; -import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Display; -import org.eclipse.ui.IEditorSite; - -import java.util.ArrayList; -import java.util.List; - -/** - * The {@link GestureManager} is is the central manager of gestures; it is responsible - * for recognizing when particular gestures should begin and terminate. It - * listens to the drag, mouse and keyboard systems to find out when to start - * gestures and in order to update the gestures along the way. - */ -public class GestureManager { - /** The canvas which owns this GestureManager. */ - private final LayoutCanvas mCanvas; - - /** The currently executing gesture, or null. */ - private Gesture mCurrentGesture; - - /** A listener for drop target events. */ - private final DropTargetListener mDropListener = new CanvasDropListener(); - - /** A listener for drag source events. */ - private final DragSourceListener mDragSourceListener = new CanvasDragSourceListener(); - - /** Tooltip shown during the gesture, or null */ - private GestureToolTip mTooltip; - - /** - * The list of overlays associated with {@link #mCurrentGesture}. Will be - * null before it has been initialized lazily by the paint routine (the - * initialized value can never be null, but it can be an empty collection). - */ - private List<Overlay> mOverlays; - - /** - * Most recently seen mouse position (x coordinate). We keep a copy of this - * value since we sometimes need to know it when we aren't told about the - * mouse position (such as when a keystroke is received, such as an arrow - * key in order to tweak the current drop position) - */ - protected int mLastMouseX; - - /** - * Most recently seen mouse position (y coordinate). We keep a copy of this - * value since we sometimes need to know it when we aren't told about the - * mouse position (such as when a keystroke is received, such as an arrow - * key in order to tweak the current drop position) - */ - protected int mLastMouseY; - - /** - * Most recently seen mouse mask. We keep a copy of this since in some - * scenarios (such as on a drag gesture) we don't get access to it. - */ - protected int mLastStateMask; - - /** - * Listener for mouse motion, click and keyboard events. - */ - private Listener mListener; - - /** - * When we the drag leaves, we don't know if that's the last we'll see of - * this drag or if it's just temporarily outside the canvas and it will - * return. We want to restore it if it comes back. This is also necessary - * because even on a drop we'll receive a - * {@link DropTargetListener#dragLeave} right before the drop, and we need - * to restore it in the drop. Therefore, when we lose a {@link DropGesture} - * to a {@link DropTargetListener#dragLeave}, we store a reference to the - * current gesture as a {@link #mZombieGesture}, since the gesture is dead - * but might be brought back to life if we see a subsequent - * {@link DropTargetListener#dragEnter} before another gesture begins. - */ - private DropGesture mZombieGesture; - - /** - * Flag tracking whether we've set a message or error message on the global status - * line (since we only want to clear that message if we have set it ourselves). - * This is the actual message rather than a boolean such that (if we can get our - * hands on the global message) we can check to see if the current message is the - * one we set and only in that case clear it when it is no longer applicable. - */ - private String mDisplayingMessage; - - /** - * Constructs a new {@link GestureManager} for the given - * {@link LayoutCanvas}. - * - * @param canvas The canvas which controls this {@link GestureManager} - */ - public GestureManager(LayoutCanvas canvas) { - mCanvas = canvas; - } - - /** - * Returns the canvas associated with this GestureManager. - * - * @return The {@link LayoutCanvas} associated with this GestureManager. - * Never null. - */ - public LayoutCanvas getCanvas() { - return mCanvas; - } - - /** - * Returns the current gesture, if one is in progress, and otherwise returns - * null. - * - * @return The current gesture or null. - */ - public Gesture getCurrentGesture() { - return mCurrentGesture; - } - - /** - * Paints the overlays associated with the current gesture, if any. - * - * @param gc The graphics object to paint into. - */ - public void paint(GC gc) { - if (mCurrentGesture == null) { - return; - } - - if (mOverlays == null) { - mOverlays = mCurrentGesture.createOverlays(); - Device device = gc.getDevice(); - for (Overlay overlay : mOverlays) { - overlay.create(device); - } - } - for (Overlay overlay : mOverlays) { - overlay.paint(gc); - } - } - - /** - * Registers all the listeners needed by the {@link GestureManager}. - * - * @param dragSource The drag source in the {@link LayoutCanvas} to listen - * to. - * @param dropTarget The drop target in the {@link LayoutCanvas} to listen - * to. - */ - public void registerListeners(DragSource dragSource, DropTarget dropTarget) { - assert mListener == null; - mListener = new Listener(); - mCanvas.addMouseMoveListener(mListener); - mCanvas.addMouseListener(mListener); - mCanvas.addKeyListener(mListener); - - if (dragSource != null) { - dragSource.addDragListener(mDragSourceListener); - } - if (dropTarget != null) { - dropTarget.addDropListener(mDropListener); - } - } - - /** - * Unregisters all the listeners previously registered by - * {@link #registerListeners}. - * - * @param dragSource The drag source in the {@link LayoutCanvas} to stop - * listening to. - * @param dropTarget The drop target in the {@link LayoutCanvas} to stop - * listening to. - */ - public void unregisterListeners(DragSource dragSource, DropTarget dropTarget) { - if (mCanvas.isDisposed()) { - // If the LayoutCanvas is already disposed, we shouldn't try to unregister - // the listeners; they are already not active and an attempt to remove the - // listener will throw a widget-is-disposed exception. - mListener = null; - return; - } - - if (mListener != null) { - mCanvas.removeMouseMoveListener(mListener); - mCanvas.removeMouseListener(mListener); - mCanvas.removeKeyListener(mListener); - mListener = null; - } - - if (dragSource != null) { - dragSource.removeDragListener(mDragSourceListener); - } - if (dropTarget != null) { - dropTarget.removeDropListener(mDropListener); - } - } - - /** - * Starts the given gesture. - * - * @param mousePos The most recent mouse coordinate applicable to the new - * gesture, in control coordinates. - * @param gesture The gesture to initiate - */ - private void startGesture(ControlPoint mousePos, Gesture gesture, int mask) { - if (mCurrentGesture != null) { - finishGesture(mousePos, true); - assert mCurrentGesture == null; - } - - if (gesture != null) { - mCurrentGesture = gesture; - mCurrentGesture.begin(mousePos, mask); - } - } - - /** - * Updates the current gesture, if any, for the given event. - * - * @param mousePos The most recent mouse coordinate applicable to the new - * gesture, in control coordinates. - * @param event The event corresponding to this update. May be null. Don't - * make any assumptions about the type of this event - for - * example, it may not always be a MouseEvent, it could be a - * DragSourceEvent, etc. - */ - private void updateMouse(ControlPoint mousePos, TypedEvent event) { - if (mCurrentGesture != null) { - mCurrentGesture.update(mousePos); - } - } - - /** - * Finish the given gesture, either from successful completion or from - * cancellation. - * - * @param mousePos The most recent mouse coordinate applicable to the new - * gesture, in control coordinates. - * @param canceled True if and only if the gesture was canceled. - */ - private void finishGesture(ControlPoint mousePos, boolean canceled) { - if (mCurrentGesture != null) { - mCurrentGesture.end(mousePos, canceled); - if (mOverlays != null) { - for (Overlay overlay : mOverlays) { - overlay.dispose(); - } - mOverlays = null; - } - mCurrentGesture = null; - mZombieGesture = null; - mLastStateMask = 0; - updateMessage(null); - updateCursor(mousePos); - mCanvas.redraw(); - } - } - - /** - * Update the cursor to show the type of operation we expect on a mouse press: - * <ul> - * <li>Over a selection handle, show a directional cursor depending on the position of - * the selection handle - * <li>Over a widget, show a move (hand) cursor - * <li>Otherwise, show the default arrow cursor - * </ul> - */ - void updateCursor(ControlPoint controlPoint) { - // We don't hover on the root since it's not a widget per see and it is always there. - SelectionManager selectionManager = mCanvas.getSelectionManager(); - - if (!selectionManager.isEmpty()) { - Display display = mCanvas.getDisplay(); - Pair<SelectionItem, SelectionHandle> handlePair = - selectionManager.findHandle(controlPoint); - if (handlePair != null) { - SelectionHandle handle = handlePair.getSecond(); - int cursorType = handle.getSwtCursorType(); - Cursor cursor = display.getSystemCursor(cursorType); - if (cursor != mCanvas.getCursor()) { - mCanvas.setCursor(cursor); - } - return; - } - - // See if it's over a selected view - LayoutPoint layoutPoint = controlPoint.toLayout(); - for (SelectionItem item : selectionManager.getSelections()) { - if (item.getRect().contains(layoutPoint.x, layoutPoint.y) - && !item.isRoot()) { - Cursor cursor = display.getSystemCursor(SWT.CURSOR_HAND); - if (cursor != mCanvas.getCursor()) { - mCanvas.setCursor(cursor); - } - return; - } - } - } - - if (mCanvas.getCursor() != null) { - mCanvas.setCursor(null); - } - } - - /** - * Update the Eclipse status message with any feedback messages from the given - * {@link DropFeedback} object, or clean up if there is no more feedback to process - * @param feedback the feedback whose message we want to display, or null to clear the - * message if previously set - */ - void updateMessage(DropFeedback feedback) { - IEditorSite editorSite = mCanvas.getEditorDelegate().getEditor().getEditorSite(); - IStatusLineManager status = editorSite.getActionBars().getStatusLineManager(); - if (feedback == null) { - if (mDisplayingMessage != null) { - status.setMessage(null); - status.setErrorMessage(null); - mDisplayingMessage = null; - } - } else if (feedback.errorMessage != null) { - if (!feedback.errorMessage.equals(mDisplayingMessage)) { - mDisplayingMessage = feedback.errorMessage; - status.setErrorMessage(mDisplayingMessage); - } - } else if (feedback.message != null) { - if (!feedback.message.equals(mDisplayingMessage)) { - mDisplayingMessage = feedback.message; - status.setMessage(mDisplayingMessage); - } - } else if (mDisplayingMessage != null) { - // TODO: Can we check the existing message and only clear it if it's the - // same as the one we set? - mDisplayingMessage = null; - status.setMessage(null); - status.setErrorMessage(null); - } - - // Tooltip - if (feedback != null && feedback.tooltip != null) { - Pair<Boolean,Boolean> position = mCurrentGesture.getTooltipPosition(); - boolean below = position.getFirst(); - if (feedback.tooltipY != null) { - below = feedback.tooltipY == SegmentType.BOTTOM; - } - boolean toRightOf = position.getSecond(); - if (feedback.tooltipX != null) { - toRightOf = feedback.tooltipX == SegmentType.RIGHT; - } - if (mTooltip == null) { - mTooltip = new GestureToolTip(mCanvas, below, toRightOf); - } - mTooltip.update(feedback.tooltip, below, toRightOf); - } else if (mTooltip != null) { - mTooltip.dispose(); - mTooltip = null; - } - } - - /** - * Returns the current mouse position as a {@link ControlPoint} - * - * @return the current mouse position as a {@link ControlPoint} - */ - public ControlPoint getCurrentControlPoint() { - return ControlPoint.create(mCanvas, mLastMouseX, mLastMouseY); - } - - /** - * Returns the current SWT modifier key mask as an {@link IViewRule} modifier mask - * - * @return the current SWT modifier key mask as an {@link IViewRule} modifier mask - */ - public int getRuleModifierMask() { - int swtMask = mLastStateMask; - int modifierMask = 0; - if ((swtMask & SWT.MOD1) != 0) { - modifierMask |= DropFeedback.MODIFIER1; - } - if ((swtMask & SWT.MOD2) != 0) { - modifierMask |= DropFeedback.MODIFIER2; - } - if ((swtMask & SWT.MOD3) != 0) { - modifierMask |= DropFeedback.MODIFIER3; - } - return modifierMask; - } - - /** - * Helper class which implements the {@link MouseMoveListener}, - * {@link MouseListener} and {@link KeyListener} interfaces. - */ - private class Listener implements MouseMoveListener, MouseListener, MouseTrackListener, - KeyListener { - - // --- MouseMoveListener --- - - @Override - public void mouseMove(MouseEvent e) { - mLastMouseX = e.x; - mLastMouseY = e.y; - mLastStateMask = e.stateMask; - - ControlPoint controlPoint = ControlPoint.create(mCanvas, e); - if ((e.stateMask & SWT.BUTTON_MASK) != 0) { - if (mCurrentGesture != null) { - updateMouse(controlPoint, e); - mCanvas.redraw(); - } - } else { - updateCursor(controlPoint); - mCanvas.hover(e); - mCanvas.getPreviewManager().moved(controlPoint); - } - } - - // --- MouseListener --- - - @Override - public void mouseUp(MouseEvent e) { - ControlPoint mousePos = ControlPoint.create(mCanvas, e); - - if (mCurrentGesture == null) { - // If clicking on a configuration preview, just process it there - if (mCanvas.getPreviewManager().click(mousePos)) { - return; - } - - // Just a click, select - Pair<SelectionItem, SelectionHandle> handlePair = - mCanvas.getSelectionManager().findHandle(mousePos); - if (handlePair == null) { - mCanvas.getSelectionManager().select(e); - } - } - if (mCurrentGesture == null) { - updateCursor(mousePos); - } else if (mCurrentGesture instanceof DropGesture) { - // Mouse Up shouldn't be delivered in the middle of a drag & drop - - // but this can happen on some versions of Linux - // (see http://code.google.com/p/android/issues/detail?id=19057 ) - // and if we process the mouseUp it will abort the remainder of - // the drag & drop operation, so ignore this event! - } else { - finishGesture(mousePos, false); - } - mCanvas.redraw(); - } - - @Override - public void mouseDown(MouseEvent e) { - mLastMouseX = e.x; - mLastMouseY = e.y; - mLastStateMask = e.stateMask; - - // Not yet used. Should be, for Mac and Linux. - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - // SWT delivers a double click event even if you click two different buttons - // in rapid succession. In any case, we only want to let you double click the - // first button to warp to XML: - if (e.button == 1) { - // Warp to the text editor and show the corresponding XML for the - // double-clicked widget - LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout(); - CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); - if (vi != null) { - mCanvas.show(vi); - } - } - } - - // --- MouseTrackListener --- - - @Override - public void mouseEnter(MouseEvent e) { - ControlPoint mousePos = ControlPoint.create(mCanvas, e); - mCanvas.getPreviewManager().enter(mousePos); - } - - @Override - public void mouseExit(MouseEvent e) { - ControlPoint mousePos = ControlPoint.create(mCanvas, e); - mCanvas.getPreviewManager().exit(mousePos); - } - - @Override - public void mouseHover(MouseEvent e) { - } - - // --- KeyListener --- - - @Override - public void keyPressed(KeyEvent e) { - mLastStateMask = e.stateMask; - // Workaround for the fact that in keyPressed the current state - // mask is not yet updated - if (e.keyCode == SWT.SHIFT) { - mLastStateMask |= SWT.MOD2; - } - if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) { - if (e.keyCode == SWT.COMMAND) { - mLastStateMask |= SWT.MOD1; - } - } else { - if (e.keyCode == SWT.CTRL) { - mLastStateMask |= SWT.MOD1; - } - } - - // Give gestures a first chance to see and consume the key press - if (mCurrentGesture != null) { - // unless it's "Escape", which cancels the gesture - if (e.keyCode == SWT.ESC) { - ControlPoint controlPoint = ControlPoint.create(mCanvas, - mLastMouseX, mLastMouseY); - finishGesture(controlPoint, true); - return; - } - - if (mCurrentGesture.keyPressed(e)) { - return; - } - } - - // Fall back to canvas actions for the key press - mCanvas.handleKeyPressed(e); - } - - @Override - public void keyReleased(KeyEvent e) { - mLastStateMask = e.stateMask; - // Workaround for the fact that in keyPressed the current state - // mask is not yet updated - if (e.keyCode == SWT.SHIFT) { - mLastStateMask &= ~SWT.MOD2; - } - if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) { - if (e.keyCode == SWT.COMMAND) { - mLastStateMask &= ~SWT.MOD1; - } - } else { - if (e.keyCode == SWT.CTRL) { - mLastStateMask &= ~SWT.MOD1; - } - } - - if (mCurrentGesture != null) { - mCurrentGesture.keyReleased(e); - } - } - } - - /** Listener for Drag & Drop events. */ - private class CanvasDropListener implements DropTargetListener { - public CanvasDropListener() { - } - - /** - * The cursor has entered the drop target boundaries. {@inheritDoc} - */ - @Override - public void dragEnter(DropTargetEvent event) { - mCanvas.showInvisibleViews(true); - mCanvas.getEditorDelegate().getGraphicalEditor().dismissHoverPalette(); - - if (mCurrentGesture == null) { - Gesture newGesture = mZombieGesture; - if (newGesture == null) { - newGesture = new MoveGesture(mCanvas); - } else { - mZombieGesture = null; - } - startGesture(ControlPoint.create(mCanvas, event), - newGesture, 0); - } - - if (mCurrentGesture instanceof DropGesture) { - ((DropGesture) mCurrentGesture).dragEnter(event); - } - } - - /** - * The cursor is moving over the drop target. {@inheritDoc} - */ - @Override - public void dragOver(DropTargetEvent event) { - if (mCurrentGesture instanceof DropGesture) { - ((DropGesture) mCurrentGesture).dragOver(event); - } - } - - /** - * The cursor has left the drop target boundaries OR data is about to be - * dropped. {@inheritDoc} - */ - @Override - public void dragLeave(DropTargetEvent event) { - if (mCurrentGesture instanceof DropGesture) { - DropGesture dropGesture = (DropGesture) mCurrentGesture; - dropGesture.dragLeave(event); - finishGesture(ControlPoint.create(mCanvas, event), true); - mZombieGesture = dropGesture; - } - - mCanvas.showInvisibleViews(false); - } - - /** - * The drop is about to be performed. The drop target is given a last - * chance to change the nature of the drop. {@inheritDoc} - */ - @Override - public void dropAccept(DropTargetEvent event) { - Gesture gesture = mCurrentGesture != null ? mCurrentGesture : mZombieGesture; - if (gesture instanceof DropGesture) { - ((DropGesture) gesture).dropAccept(event); - } - } - - /** - * The data is being dropped. {@inheritDoc} - */ - @Override - public void drop(final DropTargetEvent event) { - // See if we had a gesture just prior to the drop (we receive a dragLeave - // right before the drop which we don't know whether means the cursor has - // left the canvas for good or just before a drop) - Gesture gesture = mCurrentGesture != null ? mCurrentGesture : mZombieGesture; - mZombieGesture = null; - - if (gesture instanceof DropGesture) { - ((DropGesture) gesture).drop(event); - - finishGesture(ControlPoint.create(mCanvas, event), true); - } - } - - /** - * The operation being performed has changed (e.g. modifier key). - * {@inheritDoc} - */ - @Override - public void dragOperationChanged(DropTargetEvent event) { - if (mCurrentGesture instanceof DropGesture) { - ((DropGesture) mCurrentGesture).dragOperationChanged(event); - } - } - } - - /** - * Our canvas {@link DragSourceListener}. Handles drag being started and - * finished and generating the drag data. - */ - private class CanvasDragSourceListener implements DragSourceListener { - - /** - * The current selection being dragged. This may be a subset of the - * canvas selection due to the "sanitize" pass. Can be empty but never - * null. - */ - private final ArrayList<SelectionItem> mDragSelection = new ArrayList<SelectionItem>(); - - private SimpleElement[] mDragElements; - - /** - * The user has begun the actions required to drag the widget. - * <p/> - * Initiate a drag only if there is one or more item selected. If - * there's none, try to auto-select the one under the cursor. - * {@inheritDoc} - */ - @Override - public void dragStart(DragSourceEvent e) { - LayoutPoint p = LayoutPoint.create(mCanvas, e); - ControlPoint controlPoint = ControlPoint.create(mCanvas, e); - SelectionManager selectionManager = mCanvas.getSelectionManager(); - - // See if the mouse is over a selection handle; if so, start a resizing - // gesture. - Pair<SelectionItem, SelectionHandle> handle = - selectionManager.findHandle(controlPoint); - if (handle != null) { - startGesture(controlPoint, new ResizeGesture(mCanvas, handle.getFirst(), - handle.getSecond()), mLastStateMask); - e.detail = DND.DROP_NONE; - e.doit = false; - mCanvas.redraw(); - return; - } - - // We need a selection (simple or multiple) to do any transfer. - // If there's a selection *and* the cursor is over this selection, - // use all the currently selected elements. - // If there is no selection or the cursor is not over a selected - // element, *change* the selection to match the element under the - // cursor and use that. If nothing can be selected, abort the drag - // operation. - List<SelectionItem> selections = selectionManager.getSelections(); - mDragSelection.clear(); - SelectionItem primary = null; - - if (!selections.isEmpty()) { - // Is the cursor on top of a selected element? - boolean insideSelection = false; - - for (SelectionItem cs : selections) { - if (!cs.isRoot() && cs.getRect().contains(p.x, p.y)) { - primary = cs; - insideSelection = true; - break; - } - } - - if (!insideSelection) { - CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); - if (vi != null && !vi.isRoot() && !vi.isHidden()) { - primary = selectionManager.selectSingle(vi); - insideSelection = true; - } - } - - if (insideSelection) { - // We should now have a proper selection that matches the - // cursor. Let's use this one. We make a copy of it since - // the "sanitize" pass below might remove some of the - // selected objects. - if (selections.size() == 1) { - // You are dragging just one element - this might or - // might not be the root, but if it's the root that is - // fine since we will let you drag the root if it is the - // only thing you are dragging. - mDragSelection.addAll(selections); - } else { - // Only drag non-root items. - for (SelectionItem cs : selections) { - if (!cs.isRoot() && !cs.isHidden()) { - mDragSelection.add(cs); - } else if (cs == primary) { - primary = null; - } - } - } - } - } - - // If you are dragging a non-selected item, select it - if (mDragSelection.isEmpty()) { - CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); - if (vi != null && !vi.isRoot() && !vi.isHidden()) { - primary = selectionManager.selectSingle(vi); - mDragSelection.addAll(selections); - } - } - - SelectionManager.sanitize(mDragSelection); - - e.doit = !mDragSelection.isEmpty(); - int imageCount = mDragSelection.size(); - if (e.doit) { - mDragElements = SelectionItem.getAsElements(mDragSelection, primary); - GlobalCanvasDragInfo.getInstance().startDrag(mDragElements, - mDragSelection.toArray(new SelectionItem[imageCount]), - mCanvas, new Runnable() { - @Override - public void run() { - mCanvas.getClipboardSupport().deleteSelection("Remove", - mDragSelection); - } - }); - } - - // If you drag on the -background-, we make that into a marquee - // selection - if (!e.doit || (imageCount == 1 - && (mDragSelection.get(0).isRoot() || mDragSelection.get(0).isHidden()))) { - boolean toggle = (mLastStateMask & (SWT.CTRL | SWT.SHIFT | SWT.COMMAND)) != 0; - startGesture(controlPoint, - new MarqueeGesture(mCanvas, toggle), mLastStateMask); - e.detail = DND.DROP_NONE; - e.doit = false; - } else { - // Otherwise, the drag means you are moving something - mCanvas.showInvisibleViews(true); - startGesture(controlPoint, new MoveGesture(mCanvas), 0); - - // Render drag-images: Copy portions of the full screen render. - Image image = mCanvas.getImageOverlay().getImage(); - if (image != null) { - /** - * Transparency of the dragged image ([0-255]). We're using 30% - * translucency to make the image faint and not obscure the drag - * feedback below it. - */ - final byte DRAG_TRANSPARENCY = (byte) (0.3 * 255); - - List<Rectangle> rectangles = new ArrayList<Rectangle>(imageCount); - if (imageCount > 0) { - ImageData data = image.getImageData(); - Rectangle imageRectangle = new Rectangle(0, 0, data.width, data.height); - for (SelectionItem item : mDragSelection) { - Rectangle bounds = item.getRect(); - // Some bounds can be outside the rendered rectangle (for - // example, in an absolute layout, you can have negative - // coordinates), so create the intersection of these bounds. - Rectangle clippedBounds = imageRectangle.intersection(bounds); - rectangles.add(clippedBounds); - } - Rectangle boundingBox = ImageUtils.getBoundingRectangle(rectangles); - double scale = mCanvas.getHorizontalTransform().getScale(); - e.image = SwtUtils.drawRectangles(image, rectangles, boundingBox, scale, - DRAG_TRANSPARENCY); - - // Set the image offset such that we preserve the relative - // distance between the mouse pointer and the top left corner of - // the dragged view - int deltaX = (int) (scale * (boundingBox.x - p.x)); - int deltaY = (int) (scale * (boundingBox.y - p.y)); - e.offsetX = -deltaX; - e.offsetY = -deltaY; - - // View rules may need to know it as well - GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance(); - Rect dragBounds = null; - int width = (int) (scale * boundingBox.width); - int height = (int) (scale * boundingBox.height); - dragBounds = new Rect(deltaX, deltaY, width, height); - dragInfo.setDragBounds(dragBounds); - - // Record the baseline such that we can perform baseline alignment - // on the node as it's dragged around - NodeProxy firstNode = - mCanvas.getNodeFactory().create(mDragSelection.get(0).getViewInfo()); - dragInfo.setDragBaseline(firstNode.getBaseline()); - } - } - } - - // No hover during drag (since no mouse over events are delivered - // during a drag to keep the hovers up to date anyway) - mCanvas.clearHover(); - - mCanvas.redraw(); - } - - /** - * Callback invoked when data is needed for the event, typically right - * before drop. The drop side decides what type of transfer to use and - * this side must now provide the adequate data. {@inheritDoc} - */ - @Override - public void dragSetData(DragSourceEvent e) { - if (TextTransfer.getInstance().isSupportedType(e.dataType)) { - e.data = SelectionItem.getAsText(mCanvas, mDragSelection); - return; - } - - if (SimpleXmlTransfer.getInstance().isSupportedType(e.dataType)) { - e.data = mDragElements; - return; - } - - // otherwise we failed - e.detail = DND.DROP_NONE; - e.doit = false; - } - - /** - * Callback invoked when the drop has been finished either way. On a - * successful move, remove the originating elements. - */ - @Override - public void dragFinished(DragSourceEvent e) { - // Clear the selection - mDragSelection.clear(); - mDragElements = null; - GlobalCanvasDragInfo.getInstance().stopDrag(); - - finishGesture(ControlPoint.create(mCanvas, e), e.detail == DND.DROP_NONE); - mCanvas.showInvisibleViews(false); - mCanvas.redraw(); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureToolTip.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureToolTip.java deleted file mode 100644 index a49e79cbf..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureToolTip.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * 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.graphics.Font; -import org.eclipse.swt.graphics.FontData; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** - * A dedicated tooltip used during gestures, for example to show the resize dimensions. - * <p> - * This is necessary because {@link org.eclipse.jface.window.ToolTip} causes flicker when - * used to dynamically update the position and text of the tip, and it does not seem to - * have setter methods to update the text or position without recreating the tip. - */ -public class GestureToolTip { - /** Minimum number of milliseconds to wait between alignment changes */ - private static final int TIMEOUT_MS = 750; - - /** - * The alpha to use for the tooltip window (which sadly will apply to the tooltip text - * as well.) - */ - private static final int SHELL_TRANSPARENCY = 220; - - /** The size of the font displayed in the tooltip */ - private static final int FONT_SIZE = 9; - - /** Horizontal delta from the mouse cursor to shift the tooltip by */ - private static final int OFFSET_X = 20; - - /** Vertical delta from the mouse cursor to shift the tooltip by */ - private static final int OFFSET_Y = 20; - - /** The label which displays the tooltip */ - private CLabel mLabel; - - /** The shell holding the tooltip */ - private Shell mShell; - - /** The font shown in the label; held here such that it can be disposed of after use */ - private Font mFont; - - /** Is the tooltip positioned below the given anchor? */ - private boolean mBelow; - - /** Is the tooltip positioned to the right of the given anchor? */ - private boolean mToRightOf; - - /** Is an alignment change pending? */ - private boolean mTimerPending; - - /** The new value for {@link #mBelow} when the timer expires */ - private boolean mPendingBelow; - - /** The new value for {@link #mToRightOf} when the timer expires */ - private boolean mPendingRight; - - /** The time stamp (from {@link System#currentTimeMillis()} of the last alignment change */ - private long mLastAlignmentTime; - - /** - * Creates a new tooltip over the given parent with the given relative position. - * - * @param parent the parent control - * @param below if true, display the tooltip below the mouse cursor otherwise above - * @param toRightOf if true, display the tooltip to the right of the mouse cursor, - * otherwise to the left - */ - public GestureToolTip(Composite parent, boolean below, boolean toRightOf) { - mBelow = below; - mToRightOf = toRightOf; - mLastAlignmentTime = System.currentTimeMillis(); - - mShell = new Shell(parent.getShell(), SWT.ON_TOP | SWT.TOOL | SWT.NO_FOCUS); - mShell.setLayout(new FillLayout()); - mShell.setAlpha(SHELL_TRANSPARENCY); - - Display display = parent.getDisplay(); - mLabel = new CLabel(mShell, SWT.SHADOW_NONE); - mLabel.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); - mLabel.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); - - Font systemFont = display.getSystemFont(); - FontData[] fd = systemFont.getFontData(); - for (int i = 0; i < fd.length; i++) { - fd[i].setHeight(FONT_SIZE); - } - mFont = new Font(display, fd); - mLabel.setFont(mFont); - - mShell.setVisible(false); - } - - /** - * Show the tooltip at the given position and with the given text. Note that the - * position may not be applied immediately; to prevent flicker alignment changes - * are queued up with a timer (unless it's been a while since the last change, in - * which case the update is applied immediately.) - * - * @param text the new text to be displayed - * @param below if true, display the tooltip below the mouse cursor otherwise above - * @param toRightOf if true, display the tooltip to the right of the mouse cursor, - * otherwise to the left - */ - public void update(final String text, boolean below, boolean toRightOf) { - // If the alignment has not changed recently, just apply the change immediately - // instead of within a delay - if (!mTimerPending && (below != mBelow || toRightOf != mToRightOf) - && (System.currentTimeMillis() - mLastAlignmentTime >= TIMEOUT_MS)) { - mBelow = below; - mToRightOf = toRightOf; - mLastAlignmentTime = System.currentTimeMillis(); - } - - Point location = mShell.getDisplay().getCursorLocation(); - - mLabel.setText(text); - - // Pack the label to its minimum size -- unless we are positioning the tooltip - // on the left. Because of the way SWT works (at least on the OSX) this sometimes - // creates flicker, because when we switch to a longer string (such as when - // switching from "52dp" to "wrap_content" during a resize) the window size will - // change first, and then the location will update later - so there will be a - // brief flash of the longer label before it is moved to the right position on the - // left. To work around this, we simply pass false to pack such that it will reuse - // its cached size, which in practice means that for labels on the right, the - // label will grow but not shrink. - // This workaround is disabled because it doesn't work well in Eclipse 3.5; the - // labels don't grow when they should. Re-enable when we drop 3.5 support. - //boolean changed = mToRightOf; - boolean changed = true; - - mShell.pack(changed); - Point size = mShell.getSize(); - - // Position the tooltip to the left or right, and above or below, according - // to the saved state of these flags, not the current parameters. We don't want - // to flicker, instead we react on a timer to changes in alignment below. - if (mBelow) { - location.y += OFFSET_Y; - } else { - location.y -= OFFSET_Y; - location.y -= size.y; - } - - if (mToRightOf) { - location.x += OFFSET_X; - } else { - location.x -= OFFSET_X; - location.x -= size.x; - } - - mShell.setLocation(location); - - if (!mShell.isVisible()) { - mShell.setVisible(true); - } - - // Has the orientation changed? - mPendingBelow = below; - mPendingRight = toRightOf; - if (below != mBelow || toRightOf != mToRightOf) { - // Yes, so schedule a timer (unless one is already scheduled) - if (!mTimerPending) { - mTimerPending = true; - final Runnable timer = new Runnable() { - @Override - public void run() { - mTimerPending = false; - // Check whether the alignment is still different than the target - // (since we may change back and forth repeatedly during the timeout) - if (mBelow != mPendingBelow || mToRightOf != mPendingRight) { - mBelow = mPendingBelow; - mToRightOf = mPendingRight; - mLastAlignmentTime = System.currentTimeMillis(); - if (mShell != null && mShell.isVisible()) { - update(text, mBelow, mToRightOf); - } - } - } - }; - mShell.getDisplay().timerExec(TIMEOUT_MS, timer); - } - } - } - - /** Hide the tooltip and dispose of any associated resources */ - public void dispose() { - mShell.dispose(); - mFont.dispose(); - - mShell = null; - mFont = null; - mLabel = null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GlobalCanvasDragInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GlobalCanvasDragInfo.java deleted file mode 100644 index b918b00bf..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GlobalCanvasDragInfo.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.Rect; - - -/** - * This singleton is used to keep track of drag'n'drops initiated within this - * session of Eclipse. A drag can be initiated from a palette or from a canvas - * and its content is an Android View fully-qualified class name. - * <p/> - * Overall this is a workaround: the issue is that the drag'n'drop SWT API does not - * allow us to know the transfered data during the initial drag -- only when the - * data is dropped do we know what it is about (and to be more exact there is a workaround - * to do just that which works on Windows but not on Linux/Mac SWT). - * <p/> - * In the GLE we'd like to adjust drag feedback to the data being actually dropped. - * The singleton instance of this class will be used to track the data currently dragged - * off a canvas or its palette and then set back to null when the drag'n'drop is finished. - * <p/> - * Note that when a drag starts in one instance of Eclipse and the dragOver/drop is done - * in a <em>separate</em> instance of Eclipse, the dragged FQCN won't be registered here - * and will be null. - */ -final class GlobalCanvasDragInfo { - - private static final GlobalCanvasDragInfo sInstance = new GlobalCanvasDragInfo(); - - private SimpleElement[] mCurrentElements = null; - private SelectionItem[] mCurrentSelection; - private Object mSourceCanvas = null; - private Runnable mRemoveSourceHandler; - private Rect mDragBounds; - private int mDragBaseline = -1; - - /** Private constructor. Use {@link #getInstance()} to retrieve the singleton. */ - private GlobalCanvasDragInfo() { - // pass - } - - /** Returns the singleton instance. */ - public static GlobalCanvasDragInfo getInstance() { - return sInstance; - } - - /** - * Registers the XML elements being dragged. - * - * @param elements The elements being dragged - * @param primary the "primary" element among the elements; when there is a - * single item dragged this will be the same, but in - * multi-selection it will be the element under the mouse as the - * selection was initiated - * @param selection The selection (which can be null, for example when the - * user drags from the palette) - * @param sourceCanvas An object representing the source we are dragging - * from (used for identity comparisons only) - * @param removeSourceHandler A runnable (or null) which can clean up the - * source. It should only be invoked if the drag operation is a - * move, not a copy. - */ - public void startDrag( - @NonNull SimpleElement[] elements, - @Nullable SelectionItem[] selection, - @Nullable Object sourceCanvas, - @Nullable Runnable removeSourceHandler) { - mCurrentElements = elements; - mCurrentSelection = selection; - mSourceCanvas = sourceCanvas; - mRemoveSourceHandler = removeSourceHandler; - } - - /** Unregisters elements being dragged. */ - public void stopDrag() { - mCurrentElements = null; - mCurrentSelection = null; - mSourceCanvas = null; - mRemoveSourceHandler = null; - mDragBounds = null; - } - - public boolean isDragging() { - return mCurrentElements != null; - } - - /** Returns the elements being dragged. */ - @NonNull - public SimpleElement[] getCurrentElements() { - return mCurrentElements; - } - - /** Returns the selection originally dragged. - * Can be null if the drag did not start in a canvas. - */ - public SelectionItem[] getCurrentSelection() { - return mCurrentSelection; - } - - /** - * Returns the object that call {@link #startDrag(SimpleElement[], SelectionItem[], Object)}. - * Can be null. - * This is not meant to access the object indirectly, it is just meant to compare if the - * source and the destination of the drag'n'drop are the same, so object identity - * is all what matters. - */ - public Object getSourceCanvas() { - return mSourceCanvas; - } - - /** - * Removes source of the drag. This should only be called when the drag and - * drop operation is a move (not a copy). - */ - public void removeSource() { - if (mRemoveSourceHandler != null) { - mRemoveSourceHandler.run(); - mRemoveSourceHandler = null; - } - } - - /** - * Get the bounds of the drag, relative to the starting mouse position. For example, - * if you have a rectangular view of size 100x80, and you start dragging at position - * (15,20) from the top left corner of this rectangle, then the drag bounds would be - * (-15,-20, 100x80). - * <p> - * NOTE: The coordinate units will be in SWT/control pixels, not Android view pixels. - * In other words, they are affected by the canvas zoom: If you zoom the view and the - * bounds of a view grow, the drag bounds will be larger. - * - * @return the drag bounds, or null if there are no bounds for the current drag - */ - public Rect getDragBounds() { - return mDragBounds; - } - - /** - * Set the bounds of the drag, relative to the starting mouse position. See - * {@link #getDragBounds()} for details on the semantics of the drag bounds. - * - * @param dragBounds the new drag bounds, or null if there are no drag bounds - */ - public void setDragBounds(Rect dragBounds) { - mDragBounds = dragBounds; - } - - /** - * Returns the baseline of the drag, or -1 if not applicable - * - * @return the current SWT modifier key mask as an {@link IViewRule} modifier mask - */ - public int getDragBaseline() { - return mDragBaseline; - } - - /** - * Sets the baseline of the drag - * - * @param baseline the new baseline - */ - public void setDragBaseline(int baseline) { - mDragBaseline = baseline; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java deleted file mode 100644 index 0f5762da6..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ /dev/null @@ -1,2937 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.ANDROID_PKG; -import static com.android.SdkConstants.ANDROID_STRING_PREFIX; -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_CONTEXT; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.FD_GEN_SOURCES; -import static com.android.SdkConstants.GRID_LAYOUT; -import static com.android.SdkConstants.SCROLL_VIEW; -import static com.android.SdkConstants.STRING_PREFIX; -import static com.android.SdkConstants.VALUE_FALSE; -import static com.android.SdkConstants.VALUE_FILL_PARENT; -import static com.android.SdkConstants.VALUE_MATCH_PARENT; -import static com.android.SdkConstants.VALUE_WRAP_CONTENT; -import static com.android.ide.common.rendering.RenderSecurityManager.ENABLED_PROPERTY; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE_STATE; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_FOLDER; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_TARGET; -import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor.viewNeedsPackage; -import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_EAST; -import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_WEST; -import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_COLLAPSED; -import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_OPEN; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.layout.BaseLayoutRule; -import com.android.ide.common.rendering.LayoutLibrary; -import com.android.ide.common.rendering.RenderSecurityException; -import com.android.ide.common.rendering.RenderSecurityManager; -import com.android.ide.common.rendering.StaticRenderSession; -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.common.rendering.api.LayoutLog; -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.SessionParams.RenderingMode; -import com.android.ide.common.resources.ResourceRepository; -import com.android.ide.common.resources.ResourceResolver; -import com.android.ide.common.resources.configuration.FolderConfiguration; -import com.android.ide.common.sdk.LoadStatus; -import com.android.ide.eclipse.adt.AdtConstants; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlDelegate; -import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor; -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.configuration.Configuration; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationMatcher; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog; -import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.PaletteControl.PalettePage; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; -import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; -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.resources.ResourceHelper; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; -import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; -import com.android.ide.eclipse.adt.internal.sdk.Sdk; -import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; -import com.android.resources.Density; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.sdklib.IAndroidTarget; -import com.android.tools.lint.detector.api.LintUtils; -import com.android.utils.Pair; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.core.runtime.Path; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaModelMarker; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IPackageFragment; -import org.eclipse.jdt.core.IPackageFragmentRoot; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.internal.ui.preferences.BuildPathsPropertyPage; -import org.eclipse.jdt.ui.actions.OpenNewClassWizardAction; -import org.eclipse.jdt.ui.wizards.NewClassWizardPage; -import org.eclipse.jface.action.MenuManager; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.preference.IPreferenceStore; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.source.ISourceViewer; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.custom.StyleRange; -import org.eclipse.swt.custom.StyledText; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.text.edits.MalformedTreeException; -import org.eclipse.text.edits.MultiTextEdit; -import org.eclipse.text.edits.ReplaceEdit; -import org.eclipse.ui.IActionBars; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IEditorSite; -import org.eclipse.ui.INullSelectionListener; -import org.eclipse.ui.ISelectionListener; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchPart; -import org.eclipse.ui.IWorkbenchPartSite; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.dialogs.PreferencesUtil; -import org.eclipse.ui.ide.IDE; -import org.eclipse.ui.part.EditorPart; -import org.eclipse.ui.part.FileEditorInput; -import org.eclipse.ui.part.IPageSite; -import org.eclipse.ui.part.PageBookView; -import org.eclipse.wb.core.controls.flyout.FlyoutControlComposite; -import org.eclipse.wb.core.controls.flyout.IFlyoutListener; -import org.eclipse.wb.core.controls.flyout.PluginFlyoutPreferences; -import org.eclipse.wb.internal.core.editor.structure.PageSiteComposite; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Graphical layout editor part, version 2. - * <p/> - * The main component of the editor part is the {@link LayoutCanvasViewer}, which - * actually delegates its work to the {@link LayoutCanvas} control. - * <p/> - * The {@link LayoutCanvasViewer} is set as the site's {@link ISelectionProvider}: - * when the selection changes in the canvas, it is thus broadcasted to anyone listening - * on the site's selection service. - * <p/> - * This part is also an {@link ISelectionListener}. It listens to the site's selection - * service and thus receives selection changes from itself as well as the associated - * outline and property sheet (these are registered by {@link LayoutEditorDelegate#delegateGetAdapter(Class)}). - * - * @since GLE2 - */ -public class GraphicalEditorPart extends EditorPart - implements IPageImageProvider, INullSelectionListener, IFlyoutListener, - ConfigurationClient { - - /* - * Useful notes: - * To understand Drag & drop: - * http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html - * - * To understand the site's selection listener, selection provider, and the - * confusion of different-yet-similarly-named interfaces, consult this: - * http://www.eclipse.org/articles/Article-WorkbenchSelections/article.html - * - * To summarize the selection mechanism: - * - The workbench site selection service can be seen as "centralized" - * service that registers selection providers and selection listeners. - * - The editor part and the outline are selection providers. - * - The editor part, the outline and the property sheet are listeners - * which all listen to each others indirectly. - */ - - /** Property key for the window preferences for the structure flyout */ - private static final String PREF_STRUCTURE = "design.structure"; //$NON-NLS-1$ - - /** Property key for the window preferences for the palette flyout */ - private static final String PREF_PALETTE = "design.palette"; //$NON-NLS-1$ - - /** - * Session-property on files which specifies the initial config state to be used on - * this file - */ - public final static QualifiedName NAME_INITIAL_STATE = - new QualifiedName(AdtPlugin.PLUGIN_ID, "initialstate");//$NON-NLS-1$ - - /** - * Session-property on files which specifies the inclusion-context (reference to another layout - * which should be "including" this layout) when the file is opened - */ - public final static QualifiedName NAME_INCLUDE = - new QualifiedName(AdtPlugin.PLUGIN_ID, "includer");//$NON-NLS-1$ - - /** Reference to the layout editor */ - private final LayoutEditorDelegate mEditorDelegate; - - /** Reference to the file being edited. Can also be used to access the {@link IProject}. */ - private IFile mEditedFile; - - /** The configuration chooser at the top of the layout editor. */ - private ConfigurationChooser mConfigChooser; - - /** The sash that splits the palette from the error view. - * The error view is shown only when needed. */ - private SashForm mSashError; - - /** The palette displayed on the left of the sash. */ - private PaletteControl mPalette; - - /** The layout canvas displayed to the right of the sash. */ - private LayoutCanvasViewer mCanvasViewer; - - /** The Rules Engine associated with this editor. It is project-specific. */ - private RulesEngine mRulesEngine; - - /** Styled text displaying the most recent error in the error view. */ - private StyledText mErrorLabel; - - /** - * The resource reference to a file that should surround this file (e.g. include this file - * visually), or null if not applicable - */ - private Reference mIncludedWithin; - - private Map<ResourceType, Map<String, ResourceValue>> mConfiguredFrameworkRes; - private Map<ResourceType, Map<String, ResourceValue>> mConfiguredProjectRes; - private ProjectCallback mProjectCallback; - private boolean mNeedsRecompute = false; - private TargetListener mTargetListener; - private ResourceResolver mResourceResolver; - private ReloadListener mReloadListener; - private int mMinSdkVersion; - private int mTargetSdkVersion; - private LayoutActionBar mActionBar; - private OutlinePage mOutlinePage; - private FlyoutControlComposite mStructureFlyout; - private FlyoutControlComposite mPaletteComposite; - private PropertyFactory mPropertyFactory; - private boolean mRenderedOnce; - private final Object mCredential = new Object(); - - /** - * Flags which tracks whether this editor is currently active which is set whenever - * {@link #activated()} is called and clear whenever {@link #deactivated()} is called. - * This is used to suppress repeated calls to {@link #activate()} to avoid doing - * unnecessary work. - */ - private boolean mActive; - - /** - * Constructs a new {@link GraphicalEditorPart} - * - * @param editorDelegate the associated XML editor delegate - */ - public GraphicalEditorPart(@NonNull LayoutEditorDelegate editorDelegate) { - mEditorDelegate = editorDelegate; - setPartName("Graphical Layout"); - } - - // ------------------------------------ - // Methods overridden from base classes - //------------------------------------ - - /** - * Initializes the editor part with a site and input. - * {@inheritDoc} - */ - @Override - public void init(IEditorSite site, IEditorInput input) throws PartInitException { - setSite(site); - useNewEditorInput(input); - - if (mTargetListener == null) { - mTargetListener = new TargetListener(); - AdtPlugin.getDefault().addTargetListener(mTargetListener); - - // Trigger a check to see if the SDK needs to be reloaded (which will - // invoke onSdkLoaded asynchronously as needed). - AdtPlugin.getDefault().refreshSdk(); - } - } - - private void useNewEditorInput(IEditorInput input) throws PartInitException { - // The contract of init() mentions we need to fail if we can't understand the input. - if (!(input instanceof FileEditorInput)) { - throw new PartInitException("Input is not of type FileEditorInput: " + //$NON-NLS-1$ - input == null ? "null" : input.toString()); //$NON-NLS-1$ - } - } - - @Override - public Image getPageImage() { - return IconFactory.getInstance().getIcon("editor_page_design"); //$NON-NLS-1$ - } - - @Override - public void createPartControl(Composite parent) { - - Display d = parent.getDisplay(); - - GridLayout gl = new GridLayout(1, false); - parent.setLayout(gl); - gl.marginHeight = gl.marginWidth = 0; - - // Check whether somebody has requested an initial state for the newly opened file. - // The initial state is a serialized version of the state compatible with - // {@link ConfigurationComposite#CONFIG_STATE}. - String initialState = null; - IFile file = mEditedFile; - if (file == null) { - IEditorInput input = mEditorDelegate.getEditor().getEditorInput(); - if (input instanceof FileEditorInput) { - file = ((FileEditorInput) input).getFile(); - } - } - - if (file != null) { - try { - initialState = (String) file.getSessionProperty(NAME_INITIAL_STATE); - if (initialState != null) { - // Only use once - file.setSessionProperty(NAME_INITIAL_STATE, null); - } - } catch (CoreException e) { - AdtPlugin.log(e, "Can't read session property %1$s", NAME_INITIAL_STATE); - } - } - - IPreferenceStore preferenceStore = AdtPlugin.getDefault().getPreferenceStore(); - PluginFlyoutPreferences preferences; - preferences = new PluginFlyoutPreferences(preferenceStore, PREF_PALETTE); - preferences.initializeDefaults(DOCK_WEST, STATE_OPEN, 200); - mPaletteComposite = new FlyoutControlComposite(parent, SWT.NONE, preferences); - mPaletteComposite.setTitleText("Palette"); - mPaletteComposite.setMinWidth(100); - Composite paletteParent = mPaletteComposite.getFlyoutParent(); - Composite editorParent = mPaletteComposite.getClientParent(); - mPaletteComposite.setListener(this); - - mPaletteComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); - - PageSiteComposite paletteComposite = new PageSiteComposite(paletteParent, SWT.BORDER); - paletteComposite.setTitleText("Palette"); - paletteComposite.setTitleImage(IconFactory.getInstance().getIcon("palette")); - PalettePage decor = new PalettePage(this); - paletteComposite.setPage(decor); - mPalette = (PaletteControl) decor.getControl(); - decor.createToolbarItems(paletteComposite.getToolBar()); - - // Create the shared structure+editor area - preferences = new PluginFlyoutPreferences(preferenceStore, PREF_STRUCTURE); - preferences.initializeDefaults(DOCK_EAST, STATE_OPEN, 300); - mStructureFlyout = new FlyoutControlComposite(editorParent, SWT.NONE, preferences); - mStructureFlyout.setTitleText("Structure"); - mStructureFlyout.setMinWidth(150); - mStructureFlyout.setListener(this); - - Composite layoutBarAndCanvas = new Composite(mStructureFlyout.getClientParent(), SWT.NONE); - GridLayout gridLayout = new GridLayout(1, false); - gridLayout.horizontalSpacing = 0; - gridLayout.verticalSpacing = 0; - gridLayout.marginWidth = 0; - gridLayout.marginHeight = 0; - layoutBarAndCanvas.setLayout(gridLayout); - - mConfigChooser = new ConfigurationChooser(this, layoutBarAndCanvas, initialState); - mConfigChooser.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - - mActionBar = new LayoutActionBar(layoutBarAndCanvas, SWT.NONE, this); - GridData detailsData = new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1); - mActionBar.setLayoutData(detailsData); - if (file != null) { - mActionBar.updateErrorIndicator(file); - } - - mSashError = new SashForm(layoutBarAndCanvas, SWT.VERTICAL | SWT.BORDER); - mSashError.setLayoutData(new GridData(GridData.FILL_BOTH)); - - mCanvasViewer = new LayoutCanvasViewer(mEditorDelegate, mRulesEngine, mSashError, SWT.NONE); - mSashError.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); - - mErrorLabel = new StyledText(mSashError, SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL); - mErrorLabel.setEditable(false); - mErrorLabel.setBackground(d.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); - mErrorLabel.setForeground(d.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); - mErrorLabel.addMouseListener(new ErrorLabelListener()); - - mSashError.setWeights(new int[] { 80, 20 }); - mSashError.setMaximizedControl(mCanvasViewer.getControl()); - - // Create the structure views. We really should do this *lazily*, but that - // seems to cause a bug: property sheet won't update. Track this down later. - createStructureViews(mStructureFlyout.getFlyoutParent(), false); - showStructureViews(false, false, false); - - // Initialize the state - reloadPalette(); - - IWorkbenchPartSite site = getSite(); - site.setSelectionProvider(mCanvasViewer); - site.getPage().addSelectionListener(this); - } - - private void createStructureViews(Composite parent, boolean createPropertySheet) { - mOutlinePage = new OutlinePage(this); - mOutlinePage.setShowPropertySheet(createPropertySheet); - mOutlinePage.setShowHeader(true); - - IPageSite pageSite = new IPageSite() { - - @Override - public IWorkbenchPage getPage() { - return getSite().getPage(); - } - - @Override - public ISelectionProvider getSelectionProvider() { - return getSite().getSelectionProvider(); - } - - @Override - public Shell getShell() { - return getSite().getShell(); - } - - @Override - public IWorkbenchWindow getWorkbenchWindow() { - return getSite().getWorkbenchWindow(); - } - - @Override - public void setSelectionProvider(ISelectionProvider provider) { - getSite().setSelectionProvider(provider); - } - - @Override - public Object getAdapter(Class adapter) { - return getSite().getAdapter(adapter); - } - - @Override - public Object getService(Class api) { - return getSite().getService(api); - } - - @Override - public boolean hasService(Class api) { - return getSite().hasService(api); - } - - @Override - public void registerContextMenu(String menuId, MenuManager menuManager, - ISelectionProvider selectionProvider) { - } - - @Override - public IActionBars getActionBars() { - return null; - } - }; - mOutlinePage.init(pageSite); - mOutlinePage.createControl(parent); - mOutlinePage.addSelectionChangedListener(new ISelectionChangedListener() { - @Override - public void selectionChanged(SelectionChangedEvent event) { - getCanvasControl().getSelectionManager().setSelection(event.getSelection()); - } - }); - } - - /** Shows the embedded (within the layout editor) outline and or properties */ - void showStructureViews(final boolean showOutline, final boolean showProperties, - final boolean updateLayout) { - Display display = mConfigChooser.getDisplay(); - if (display.getThread() != Thread.currentThread()) { - display.asyncExec(new Runnable() { - @Override - public void run() { - if (!mConfigChooser.isDisposed()) { - showStructureViews(showOutline, showProperties, updateLayout); - } - } - - }); - return; - } - - boolean show = showOutline || showProperties; - - Control[] children = mStructureFlyout.getFlyoutParent().getChildren(); - if (children.length == 0) { - if (show) { - createStructureViews(mStructureFlyout.getFlyoutParent(), showProperties); - } - return; - } - - mOutlinePage.setShowPropertySheet(showProperties); - - Control control = children[0]; - if (show != control.getVisible()) { - control.setVisible(show); - mOutlinePage.setActive(show); // disable/re-enable listeners etc - if (show) { - ISelection selection = getCanvasControl().getSelectionManager().getSelection(); - mOutlinePage.selectionChanged(getEditorDelegate().getEditor(), selection); - } - if (updateLayout) { - mStructureFlyout.layout(); - } - // TODO: *dispose* the non-showing widgets to save memory? - } - } - - /** - * Returns the property factory associated with this editor - * - * @return the factory - */ - @NonNull - public PropertyFactory getPropertyFactory() { - if (mPropertyFactory == null) { - mPropertyFactory = new PropertyFactory(this); - } - - return mPropertyFactory; - } - - /** - * Invoked by {@link LayoutCanvas} to set the model (a.k.a. the root view info). - * - * @param rootViewInfo The root of the view info hierarchy. Can be null. - */ - public void setModel(CanvasViewInfo rootViewInfo) { - if (mOutlinePage != null) { - mOutlinePage.setModel(rootViewInfo); - } - } - - /** - * Listens to workbench selections that does NOT come from {@link LayoutEditorDelegate} - * (those are generated by ourselves). - * <p/> - * Selection can be null, as indicated by this class implementing - * {@link INullSelectionListener}. - */ - @Override - public void selectionChanged(IWorkbenchPart part, ISelection selection) { - Object delegate = part instanceof IEditorPart ? - LayoutEditorDelegate.fromEditor((IEditorPart) part) : null; - if (delegate == null) { - if (part instanceof PageBookView) { - PageBookView pbv = (PageBookView) part; - org.eclipse.ui.part.IPage currentPage = pbv.getCurrentPage(); - if (currentPage instanceof OutlinePage) { - LayoutCanvas canvas = getCanvasControl(); - if (canvas != null && canvas.getOutlinePage() != currentPage) { - // The notification is not for this view; ignore - // (can happen when there are multiple pages simultaneously - // visible) - return; - } - } - } - mCanvasViewer.setSelection(selection); - } - } - - @Override - public void dispose() { - getSite().getPage().removeSelectionListener(this); - getSite().setSelectionProvider(null); - - if (mTargetListener != null) { - AdtPlugin.getDefault().removeTargetListener(mTargetListener); - mTargetListener = null; - } - - if (mReloadListener != null) { - LayoutReloadMonitor.getMonitor().removeListener(mReloadListener); - mReloadListener = null; - } - - if (mCanvasViewer != null) { - mCanvasViewer.dispose(); - mCanvasViewer = null; - } - super.dispose(); - } - - /** - * Select the visual element corresponding to the given XML node - * @param xmlNode The Node whose element we want to select - */ - public void select(Node xmlNode) { - mCanvasViewer.getCanvas().getSelectionManager().select(xmlNode); - } - - // ---- Implements ConfigurationClient ---- - @Override - public void aboutToChange(int flags) { - if ((flags & CFG_TARGET) != 0) { - IAndroidTarget oldTarget = mConfigChooser.getConfiguration().getTarget(); - preRenderingTargetChangeCleanUp(oldTarget); - } - } - - @Override - public boolean changed(int flags) { - mConfiguredFrameworkRes = mConfiguredProjectRes = null; - mResourceResolver = null; - - if (mEditedFile == null) { - return true; - } - - // Before doing the normal process, test for the following case. - // - the editor is being opened (or reset for a new input) - // - the file being opened is not the best match for any possible configuration - // - another random compatible config was chosen in the config composite. - // The result is that 'match' will not be the file being edited, but because this is not - // due to a config change, we should not trigger opening the actual best match (also, - // because the editor is still opening the MatchingStrategy woudln't answer true - // and the best match file would open in a different editor). - // So the solution is that if the editor is being created, we just call recomputeLayout - // without looking for a better matching layout file. - if (mEditorDelegate.getEditor().isCreatingPages()) { - recomputeLayout(); - } else { - boolean affectsFileSelection = (flags & Configuration.MASK_FILE_ATTRS) != 0; - IFile best = null; - // get the resources of the file's project. - if (affectsFileSelection) { - best = ConfigurationMatcher.getBestFileMatch(mConfigChooser); - } - if (best != null) { - if (!best.equals(mEditedFile)) { - try { - // tell the editor that the next replacement file is due to a config - // change. - mEditorDelegate.setNewFileOnConfigChange(true); - - boolean reuseEditor = AdtPrefs.getPrefs().isSharedLayoutEditor(); - if (!reuseEditor) { - String data = ConfigurationDescription.getDescription(best); - if (data == null) { - // Not previously opened: duplicate the current state as - // much as possible - data = mConfigChooser.getConfiguration().toPersistentString(); - ConfigurationDescription.setDescription(best, data); - } - } - - // ask the IDE to open the replacement file. - IDE.openEditor(getSite().getWorkbenchWindow().getActivePage(), best, - CommonXmlEditor.ID); - - // we're done! - return reuseEditor; - } catch (PartInitException e) { - // FIXME: do something! - } - } - - // at this point, we have not opened a new file. - - // Store the state in the current file - mConfigChooser.saveConstraints(); - - // Even though the layout doesn't change, the config changed, and referenced - // resources need to be updated. - recomputeLayout(); - } else if (affectsFileSelection) { - // display the error. - Configuration configuration = mConfigChooser.getConfiguration(); - FolderConfiguration currentConfig = configuration.getFullConfig(); - displayError( - "No resources match the configuration\n" + - " \n" + - "\t%1$s\n" + - " \n" + - "Change the configuration or create:\n" + - " \n" + - "\tres/%2$s/%3$s\n" + - " \n" + - "You can also click the 'Create New...' item in the configuration " + - "dropdown menu above.", - currentConfig.toDisplayString(), - currentConfig.getFolderName(ResourceFolderType.LAYOUT), - mEditedFile.getName()); - } else { - // Something else changed, such as the theme - just recompute existing - // layout - mConfigChooser.saveConstraints(); - recomputeLayout(); - } - } - - if ((flags & CFG_TARGET) != 0) { - Configuration configuration = mConfigChooser.getConfiguration(); - IAndroidTarget target = configuration.getTarget(); - Sdk current = Sdk.getCurrent(); - if (current != null) { - AndroidTargetData targetData = current.getTargetData(target); - updateCapabilities(targetData); - } - } - - if ((flags & (CFG_DEVICE | CFG_DEVICE_STATE)) != 0) { - // When the device changes, zoom the view to fit, but only up to 100% (e.g. zoom - // out to fit the content, or zoom back in if we were zoomed out more from the - // previous view, but only up to 100% such that we never blow up pixels - if (mActionBar.isZoomingAllowed()) { - getCanvasControl().setFitScale(true, true /*allowZoomIn*/); - } - } - - reloadPalette(); - - getCanvasControl().getPreviewManager().configurationChanged(flags); - - return true; - } - - @Override - public void setActivity(@NonNull String activity) { - ManifestInfo manifest = ManifestInfo.get(mEditedFile.getProject()); - String pkg = manifest.getPackage(); - if (activity.startsWith(pkg) && activity.length() > pkg.length() - && activity.charAt(pkg.length()) == '.') { - activity = activity.substring(pkg.length()); - } - CommonXmlEditor editor = getEditorDelegate().getEditor(); - Element element = editor.getUiRootNode().getXmlDocument().getDocumentElement(); - AdtUtils.setToolsAttribute(editor, - element, "Choose Activity", ATTR_CONTEXT, - activity, false /*reveal*/, false /*append*/); - } - - /** - * Returns a {@link ProjectResources} for the framework resources based on the current - * configuration selection. - * @return the framework resources or null if not found. - */ - @Override - @Nullable - public ResourceRepository getFrameworkResources() { - return getFrameworkResources(getRenderingTarget()); - } - - /** - * Returns a {@link ProjectResources} for the framework resources of a given - * target. - * @param target the target for which to return the framework resources. - * @return the framework resources or null if not found. - */ - @Override - @Nullable - public ResourceRepository getFrameworkResources(@Nullable IAndroidTarget target) { - if (target != null) { - AndroidTargetData data = Sdk.getCurrent().getTargetData(target); - - if (data != null) { - return data.getFrameworkResources(); - } - } - - return null; - } - - @Override - @Nullable - public ProjectResources getProjectResources() { - if (mEditedFile != null) { - ResourceManager manager = ResourceManager.getInstance(); - return manager.getProjectResources(mEditedFile.getProject()); - } - - return null; - } - - - @Override - @NonNull - public Map<ResourceType, Map<String, ResourceValue>> getConfiguredFrameworkResources() { - if (mConfiguredFrameworkRes == null && mConfigChooser != null) { - ResourceRepository frameworkRes = getFrameworkResources(); - - if (frameworkRes == null) { - AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework"); - } else { - // get the framework resource values based on the current config - mConfiguredFrameworkRes = frameworkRes.getConfiguredResources( - mConfigChooser.getConfiguration().getFullConfig()); - } - } - - return mConfiguredFrameworkRes; - } - - @Override - @NonNull - public Map<ResourceType, Map<String, ResourceValue>> getConfiguredProjectResources() { - if (mConfiguredProjectRes == null && mConfigChooser != null) { - ProjectResources project = getProjectResources(); - - // get the project resource values based on the current config - mConfiguredProjectRes = project.getConfiguredResources( - mConfigChooser.getConfiguration().getFullConfig()); - } - - return mConfiguredProjectRes; - } - - @Override - public void createConfigFile() { - LayoutCreatorDialog dialog = new LayoutCreatorDialog(mConfigChooser.getShell(), - mEditedFile.getName(), mConfigChooser.getConfiguration().getFullConfig()); - if (dialog.open() != Window.OK) { - return; - } - - FolderConfiguration config = new FolderConfiguration(); - dialog.getConfiguration(config); - - // Creates a new layout file from the specified {@link FolderConfiguration}. - CreateNewConfigJob job = new CreateNewConfigJob(this, mEditedFile, config); - job.schedule(); - } - - /** - * Returns the resource name of the file that is including this current layout, if any - * (may be null) - * - * @return the resource name of an including layout, or null - */ - @Override - public Reference getIncludedWithin() { - return mIncludedWithin; - } - - @Override - @Nullable - public LayoutCanvas getCanvas() { - return getCanvasControl(); - } - - /** - * Listens to target changed in the current project, to trigger a new layout rendering. - */ - private class TargetListener implements ITargetChangeListener { - - @Override - public void onProjectTargetChange(IProject changedProject) { - if (changedProject != null && changedProject.equals(getProject())) { - updateEditor(); - } - } - - @Override - public void onTargetLoaded(IAndroidTarget loadedTarget) { - IAndroidTarget target = getRenderingTarget(); - if (target != null && target.equals(loadedTarget)) { - updateEditor(); - } - } - - @Override - public void onSdkLoaded() { - // get the current rendering target to unload it - IAndroidTarget oldTarget = getRenderingTarget(); - preRenderingTargetChangeCleanUp(oldTarget); - - computeSdkVersion(); - - // get the project target - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk != null) { - IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject()); - if (target != null) { - mConfigChooser.onSdkLoaded(target); - changed(CFG_FOLDER | CFG_TARGET); - } - } - } - - private void updateEditor() { - mEditorDelegate.getEditor().commitPages(false /* onSave */); - - // because the target changed we must reset the configured resources. - mConfiguredFrameworkRes = mConfiguredProjectRes = null; - mResourceResolver = null; - - // make sure we remove the custom view loader, since its parent class loader is the - // bridge class loader. - mProjectCallback = null; - - // recreate the ui root node always, this will also call onTargetChange - // on the config composite - mEditorDelegate.delegateInitUiRootNode(true /*force*/); - } - - private IProject getProject() { - return getEditorDelegate().getEditor().getProject(); - } - } - - /** Refresh the configured project resources associated with this editor */ - public void refreshProjectResources() { - mConfiguredProjectRes = null; - mResourceResolver = null; - } - - /** - * Returns the currently edited file - * - * @return the currently edited file, or null - */ - public IFile getEditedFile() { - return mEditedFile; - } - - /** - * Returns the project for the currently edited file, or null - * - * @return the project containing the edited file, or null - */ - public IProject getProject() { - if (mEditedFile != null) { - return mEditedFile.getProject(); - } else { - return null; - } - } - - // ---------------- - - /** - * Save operation in the Graphical Editor Part. - * <p/> - * In our workflow, the model is owned by the Structured XML Editor. - * The graphical layout editor just displays it -- thus we don't really - * save anything here. - * <p/> - * This must NOT call the parent editor part. At the contrary, the parent editor - * part will call this *after* having done the actual save operation. - * <p/> - * The only action this editor must do is mark the undo command stack as - * being no longer dirty. - */ - @Override - public void doSave(IProgressMonitor monitor) { - // TODO implement a command stack -// getCommandStack().markSaveLocation(); -// firePropertyChange(PROP_DIRTY); - } - - /** - * Save operation in the Graphical Editor Part. - * <p/> - * In our workflow, the model is owned by the Structured XML Editor. - * The graphical layout editor just displays it -- thus we don't really - * save anything here. - */ - @Override - public void doSaveAs() { - // pass - } - - /** - * In our workflow, the model is owned by the Structured XML Editor. - * The graphical layout editor just displays it -- thus we don't really - * save anything here. - */ - @Override - public boolean isDirty() { - return false; - } - - /** - * In our workflow, the model is owned by the Structured XML Editor. - * The graphical layout editor just displays it -- thus we don't really - * save anything here. - */ - @Override - public boolean isSaveAsAllowed() { - return false; - } - - @Override - public void setFocus() { - // TODO Auto-generated method stub - - } - - /** - * Responds to a page change that made the Graphical editor page the activated page. - */ - public void activated() { - if (!mActive) { - mActive = true; - - syncDockingState(); - mActionBar.updateErrorIndicator(); - - boolean changed = mConfigChooser.syncRenderState(); - if (changed) { - // Will also force recomputeLayout() - return; - } - - if (mNeedsRecompute) { - recomputeLayout(); - } - - mCanvasViewer.getCanvas().syncPreviewMode(); - } - } - - /** - * The global docking state version. This number is incremented each time - * the user customizes the window layout in any layout. - */ - private static int sDockingStateVersion; - - /** - * The window docking state version that this window is currently showing; - * when a different window is reconfigured, the global version number is - * incremented, and when this window is shown, and the current version is - * less than the global version, the window layout will be synced. - */ - private int mDockingStateVersion; - - /** - * Syncs the window docking state. - * <p> - * The layout editor lets you change the docking state -- e.g. you can minimize the - * palette, and drag the structure view to the bottom, and so on. When you restart - * the IDE, the window comes back up with your customized state. - * <p> - * <b>However</b>, when you have multiple editor files open, if you minimize the palette - * in one editor and then switch to another, the other editor will have the old window - * state. That's because each editor has its own set of windows. - * <p> - * This method fixes this. Whenever a window is shown, this method is called, and the - * docking state is synced such that the editor will match the current persistent docking - * state. - */ - private void syncDockingState() { - if (mDockingStateVersion == sDockingStateVersion) { - // No changes to apply - return; - } - mDockingStateVersion = sDockingStateVersion; - - IPreferenceStore preferenceStore = AdtPlugin.getDefault().getPreferenceStore(); - PluginFlyoutPreferences preferences; - preferences = new PluginFlyoutPreferences(preferenceStore, PREF_PALETTE); - mPaletteComposite.apply(preferences); - preferences = new PluginFlyoutPreferences(preferenceStore, PREF_STRUCTURE); - mStructureFlyout.apply(preferences); - mPaletteComposite.layout(); - mStructureFlyout.layout(); - mPaletteComposite.redraw(); // the structure view is nested within the palette - } - - /** - * Responds to a page change that made the Graphical editor page the deactivated page - */ - public void deactivated() { - mActive = false; - - LayoutCanvas canvas = getCanvasControl(); - if (canvas != null) { - canvas.deactivated(); - } - } - - /** - * Opens and initialize the editor with a new file. - * @param file the file being edited. - */ - public void openFile(IFile file) { - mEditedFile = file; - mConfigChooser.setFile(mEditedFile); - - if (mReloadListener == null) { - mReloadListener = new ReloadListener(); - LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener); - } - - if (mRulesEngine == null) { - mRulesEngine = new RulesEngine(this, mEditedFile.getProject()); - if (mCanvasViewer != null) { - mCanvasViewer.getCanvas().setRulesEngine(mRulesEngine); - } - } - - // Pick up hand-off data: somebody requesting this file to be opened may have - // requested that it should be opened as included within another file - if (mEditedFile != null) { - try { - mIncludedWithin = (Reference) mEditedFile.getSessionProperty(NAME_INCLUDE); - if (mIncludedWithin != null) { - // Only use once - mEditedFile.setSessionProperty(NAME_INCLUDE, null); - } - } catch (CoreException e) { - AdtPlugin.log(e, "Can't access session property %1$s", NAME_INCLUDE); - } - } - - computeSdkVersion(); - } - - /** - * Resets the editor with a replacement file. - * @param file the replacement file. - */ - public void replaceFile(IFile file) { - mEditedFile = file; - mConfigChooser.replaceFile(mEditedFile); - computeSdkVersion(); - } - - /** - * Resets the editor with a replacement file coming from a config change in the config - * selector. - * @param file the replacement file. - */ - public void changeFileOnNewConfig(IFile file) { - mEditedFile = file; - mConfigChooser.changeFileOnNewConfig(mEditedFile); - } - - /** - * Responds to a target change for the project of the edited file - */ - public void onTargetChange() { - AndroidTargetData targetData = mConfigChooser.onXmlModelLoaded(); - updateCapabilities(targetData); - - changed(CFG_FOLDER | CFG_TARGET); - } - - /** Updates the capabilities for the given target data (which may be null) */ - private void updateCapabilities(AndroidTargetData targetData) { - if (targetData != null) { - LayoutLibrary layoutLib = targetData.getLayoutLibrary(); - if (mIncludedWithin != null && !layoutLib.supports(Capability.EMBEDDED_LAYOUT)) { - showIn(null); - } - } - } - - /** - * Returns the {@link CommonXmlDelegate} for this editor - * - * @return the {@link CommonXmlDelegate} for this editor - */ - @NonNull - public LayoutEditorDelegate getEditorDelegate() { - return mEditorDelegate; - } - - /** - * Returns the {@link RulesEngine} associated with this editor - * - * @return the {@link RulesEngine} associated with this editor, never null - */ - public RulesEngine getRulesEngine() { - return mRulesEngine; - } - - /** - * Return the {@link LayoutCanvas} associated with this editor - * - * @return the associated {@link LayoutCanvas} - */ - public LayoutCanvas getCanvasControl() { - if (mCanvasViewer != null) { - return mCanvasViewer.getCanvas(); - } - return null; - } - - /** - * Returns the {@link UiDocumentNode} for the XML model edited by this editor - * - * @return the associated model - */ - public UiDocumentNode getModel() { - return mEditorDelegate.getUiRootNode(); - } - - /** - * Callback for XML model changed. Only update/recompute the layout if the editor is visible - */ - public void onXmlModelChanged() { - // To optimize the rendering when the user is editing in the XML pane, we don't - // refresh the editor if it's not the active part. - // - // This behavior is acceptable when the editor is the single "full screen" part - // (as in this case active means visible.) - // Unfortunately this breaks in 2 cases: - // - when performing a drag'n'drop from one editor to another, the target is not - // properly refreshed before it becomes active. - // - when duplicating the editor window and placing both editors side by side (xml in one - // and canvas in the other one), the canvas may not be refreshed when the XML is edited. - // - // TODO find a way to really query whether the pane is visible, not just active. - - if (mEditorDelegate.isGraphicalEditorActive()) { - recomputeLayout(); - } else { - // Remember we want to recompute as soon as the editor becomes active. - mNeedsRecompute = true; - } - } - - /** - * Recomputes the layout - */ - public void recomputeLayout() { - try { - if (!ensureFileValid()) { - return; - } - - UiDocumentNode model = getModel(); - LayoutCanvas canvas = mCanvasViewer.getCanvas(); - if (!ensureModelValid(model)) { - // Although we display an error, we still treat an empty document as a - // successful layout result so that we can drop new elements in it. - // - // For that purpose, create a special LayoutScene that has no image, - // no root view yet indicates success and then update the canvas with it. - - canvas.setSession( - new StaticRenderSession( - Result.Status.SUCCESS.createResult(), - null /*rootViewInfo*/, null /*image*/), - null /*explodeNodes*/, true /* layoutlib5 */); - return; - } - - LayoutLibrary layoutLib = getReadyLayoutLib(true /*displayError*/); - - if (layoutLib != null) { - // if drawing in real size, (re)set the scaling factor. - if (mActionBar.isZoomingRealSize()) { - mActionBar.computeAndSetRealScale(false /* redraw */); - } - - IProject project = mEditedFile.getProject(); - renderWithBridge(project, model, layoutLib); - - canvas.getPreviewManager().renderPreviews(); - } - } finally { - // no matter the result, we are done doing the recompute based on the latest - // resource/code change. - mNeedsRecompute = false; - } - } - - /** - * Reloads the palette - */ - public void reloadPalette() { - if (mPalette != null) { - IAndroidTarget renderingTarget = getRenderingTarget(); - if (renderingTarget != null) { - mPalette.reloadPalette(renderingTarget); - } - } - } - - /** - * Returns the {@link LayoutLibrary} associated with this editor, if it has - * been initialized already. May return null if it has not been initialized (or has - * not finished initializing). - * - * @return The {@link LayoutLibrary}, or null - */ - public LayoutLibrary getLayoutLibrary() { - return getReadyLayoutLib(false /*displayError*/); - } - - /** - * Returns the scale to multiply pixels in the layout coordinate space with to obtain - * the corresponding dip (device independent pixel) - * - * @return the scale to multiple layout coordinates with to obtain the dip position - */ - public float getDipScale() { - float dpi = mConfigChooser.getConfiguration().getDensity().getDpiValue(); - return Density.DEFAULT_DENSITY / dpi; - } - - // --- private methods --- - - /** - * Ensure that the file associated with this editor is valid (exists and is - * synchronized). Any reasons why it is not are displayed in the editor's error area. - * - * @return True if the editor is valid, false otherwise. - */ - private boolean ensureFileValid() { - // check that the resource exists. If the file is opened but the project is closed - // or deleted for some reason (changed from outside of eclipse), then this will - // return false; - if (mEditedFile.exists() == false) { - displayError("Resource '%1$s' does not exist.", - mEditedFile.getFullPath().toString()); - return false; - } - - if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) { - String message = String.format("%1$s is out of sync. Please refresh.", - mEditedFile.getName()); - - displayError(message); - - // also print it in the error console. - IProject iProject = mEditedFile.getProject(); - AdtPlugin.printErrorToConsole(iProject.getName(), message); - return false; - } - - return true; - } - - /** - * Returns a {@link LayoutLibrary} that is ready for rendering, or null if the bridge - * is not available or not ready yet (due to SDK loading still being in progress etc). - * If enabled, any reasons preventing the bridge from being returned are displayed to the - * editor's error area. - * - * @param displayError whether to display the loading error or not. - * - * @return LayoutBridge the layout bridge for rendering this editor's scene - */ - LayoutLibrary getReadyLayoutLib(boolean displayError) { - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk != null) { - IAndroidTarget target = getRenderingTarget(); - - if (target != null) { - AndroidTargetData data = currentSdk.getTargetData(target); - if (data != null) { - LayoutLibrary layoutLib = data.getLayoutLibrary(); - - if (layoutLib.getStatus() == LoadStatus.LOADED) { - return layoutLib; - } else if (displayError) { // getBridge() == null - // SDK is loaded but not the layout library! - - // check whether the bridge managed to load, or not - if (layoutLib.getStatus() == LoadStatus.LOADING) { - displayError("Eclipse is loading framework information and the layout library from the SDK folder.\n%1$s will refresh automatically once the process is finished.", - mEditedFile.getName()); - } else { - String message = layoutLib.getLoadMessage(); - displayError("Eclipse failed to load the framework information and the layout library!" + - message != null ? "\n" + message : ""); - } - } - } else { // data == null - // It can happen that the workspace refreshes while the SDK is loading its - // data, which could trigger a redraw of the opened layout if some resources - // changed while Eclipse is closed. - // In this case data could be null, but this is not an error. - // We can just silently return, as all the opened editors are automatically - // refreshed once the SDK finishes loading. - LoadStatus targetLoadStatus = currentSdk.checkAndLoadTargetData(target, null); - - // display error is asked. - if (displayError) { - String targetName = target.getName(); - switch (targetLoadStatus) { - case LOADING: - String s; - if (currentSdk.getTarget(getProject()) == target) { - s = String.format( - "The project target (%1$s) is still loading.", - targetName); - } else { - s = String.format( - "The rendering target (%1$s) is still loading.", - targetName); - } - s += "\nThe layout will refresh automatically once the process is finished."; - displayError(s); - - break; - case FAILED: // known failure - case LOADED: // success but data isn't loaded?!?! - displayError("The project target (%s) was not properly loaded.", - targetName); - break; - } - } - } - - } else if (displayError) { // target == null - displayError("The project target is not set. Right click project, choose Properties | Android."); - } - } else if (displayError) { // currentSdk == null - displayError("Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.", - mEditedFile.getName()); - } - - return null; - } - - /** - * Returns the {@link IAndroidTarget} used for the rendering. - * <p/> - * This first looks for the rendering target setup in the config UI, and if nothing has - * been setup yet, returns the target of the project. - * - * @return an IAndroidTarget object or null if no target is setup and the project has no - * target set. - * - */ - public IAndroidTarget getRenderingTarget() { - // if the SDK is null no targets are loaded. - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk == null) { - return null; - } - - // attempt to get a target from the configuration selector. - IAndroidTarget renderingTarget = mConfigChooser.getConfiguration().getTarget(); - if (renderingTarget != null) { - return renderingTarget; - } - - // fall back to the project target - if (mEditedFile != null) { - return currentSdk.getTarget(mEditedFile.getProject()); - } - - return null; - } - - /** - * Returns whether the current rendering target supports the given capability - * - * @param capability the capability to be looked up - * @return true if the current rendering target supports the given capability - */ - public boolean renderingSupports(Capability capability) { - IAndroidTarget target = getRenderingTarget(); - if (target != null) { - AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target); - LayoutLibrary layoutLib = targetData.getLayoutLibrary(); - return layoutLib.supports(capability); - } - - return false; - } - - private boolean ensureModelValid(UiDocumentNode model) { - // check there is actually a model (maybe the file is empty). - if (model.getUiChildren().size() == 0) { - if (mEditorDelegate.getEditor().isCreatingPages()) { - displayError("Loading editor"); - return false; - } - displayError( - "No XML content. Please add a root view or layout to your document."); - return false; - } - - return true; - } - - /** - * Creates a {@link RenderService} associated with this editor - * @return the render service - */ - @NonNull - public RenderService createRenderService() { - return RenderService.create(this, mCredential); - } - - /** - * Creates a {@link RenderLogger} associated with this editor - * @param name the name of the logger - * @return the new logger - */ - @NonNull - public RenderLogger createRenderLogger(String name) { - return new RenderLogger(name, mCredential); - } - - /** - * Creates a {@link RenderService} associated with this editor - * - * @param configuration the configuration to use (and fallback to editor for the rest) - * @param resolver a resource resolver to use to look up resources - * @return the render service - */ - @NonNull - public RenderService createRenderService(Configuration configuration, - ResourceResolver resolver) { - return RenderService.create(this, configuration, resolver, mCredential); - } - - private void renderWithBridge(IProject iProject, UiDocumentNode model, - LayoutLibrary layoutLib) { - LayoutCanvas canvas = getCanvasControl(); - Set<UiElementNode> explodeNodes = canvas.getNodesToExplode(); - RenderLogger logger = createRenderLogger(mEditedFile.getName()); - RenderingMode renderingMode = RenderingMode.NORMAL; - // 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 = RenderService.create(this, mCredential) - .setModel(model) - .setLog(logger) - .setRenderingMode(renderingMode) - .setIncludedWithin(mIncludedWithin) - .setNodesToExpand(explodeNodes) - .createRenderSession(); - - boolean layoutlib5 = layoutLib.supports(Capability.EMBEDDED_LAYOUT); - canvas.setSession(session, explodeNodes, layoutlib5); - - // update the UiElementNode with the layout info. - if (session != null && session.getResult().isSuccess() == false) { - // An error was generated. Print it (and any other accumulated warnings) - String errorMessage = session.getResult().getErrorMessage(); - Throwable exception = session.getResult().getException(); - if (exception != null && errorMessage == null) { - errorMessage = exception.toString(); - } - if (exception != null || (errorMessage != null && errorMessage.length() > 0)) { - logger.error(null, errorMessage, exception, null /*data*/); - } else if (!logger.hasProblems()) { - logger.error(null, "Unexpected error in rendering, no details given", - null /*data*/); - } - // These errors will be included in the log warnings which are - // displayed regardless of render success status below - } - - // We might have detected some missing classes and swapped them by a mock view, - // or run into fidelity warnings or missing resources, so emit all these - // warnings - Set<String> missingClasses = mProjectCallback.getMissingClasses(); - Set<String> brokenClasses = mProjectCallback.getUninstantiatableClasses(); - if (logger.hasProblems()) { - displayLoggerProblems(iProject, logger); - displayFailingClasses(missingClasses, brokenClasses, true); - displayUserStackTrace(logger, true); - } else if (missingClasses.size() > 0 || brokenClasses.size() > 0) { - displayFailingClasses(missingClasses, brokenClasses, false); - displayUserStackTrace(logger, true); - } else if (session != null) { - // Nope, no missing or broken classes. Clear success, congrats! - hideError(); - - // First time this layout is opened, run lint on the file (after a delay) - if (!mRenderedOnce) { - mRenderedOnce = true; - Job job = new Job("Run Lint") { - @Override - protected IStatus run(IProgressMonitor monitor) { - getEditorDelegate().delegateRunLint(); - return Status.OK_STATUS; - } - - }; - job.setSystem(true); - job.schedule(3000); // 3 seconds - } - - mConfigChooser.ensureInitialized(); - } - - model.refreshUi(); - } - - /** - * Returns the {@link ResourceResolver} for this editor - * - * @return the resolver used to resolve resources for the current configuration of - * this editor, or null - */ - public ResourceResolver getResourceResolver() { - if (mResourceResolver == null) { - String theme = mConfigChooser.getThemeName(); - if (theme == null) { - displayError("Missing theme."); - return null; - } - boolean isProjectTheme = mConfigChooser.getConfiguration().isProjectTheme(); - - Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes = - getConfiguredProjectResources(); - - // Get the framework resources - Map<ResourceType, Map<String, ResourceValue>> frameworkResources = - getConfiguredFrameworkResources(); - - if (configuredProjectRes == null) { - displayError("Missing project resources for current configuration."); - return null; - } - - if (frameworkResources == null) { - displayError("Missing framework resources."); - return null; - } - - mResourceResolver = ResourceResolver.create( - configuredProjectRes, frameworkResources, - theme, isProjectTheme); - } - - return mResourceResolver; - } - - /** Returns a project callback, and optionally resets it */ - ProjectCallback getProjectCallback(boolean reset, LayoutLibrary layoutLibrary) { - // Lazily create the project callback the first time we need it - if (mProjectCallback == null) { - ResourceManager resManager = ResourceManager.getInstance(); - IProject project = getProject(); - ProjectResources projectRes = resManager.getProjectResources(project); - mProjectCallback = new ProjectCallback(layoutLibrary, projectRes, project, - mCredential, this); - } else if (reset) { - // Also clears the set of missing/broken classes prior to rendering - mProjectCallback.getMissingClasses().clear(); - mProjectCallback.getUninstantiatableClasses().clear(); - } - - return mProjectCallback; - } - - /** - * Returns the resource name of this layout, NOT including the @layout/ prefix - * - * @return the resource name of this layout, NOT including the @layout/ prefix - */ - public String getLayoutResourceName() { - return ResourceHelper.getLayoutName(mEditedFile); - } - - /** - * Cleans up when the rendering target is about to change - * @param oldTarget the old rendering target. - */ - private void preRenderingTargetChangeCleanUp(IAndroidTarget oldTarget) { - // first clear the caches related to this file in the old target - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk != null) { - AndroidTargetData data = currentSdk.getTargetData(oldTarget); - if (data != null) { - LayoutLibrary layoutLib = data.getLayoutLibrary(); - - // layoutLib can never be null. - layoutLib.clearCaches(mEditedFile.getProject()); - } - } - - // Also remove the ProjectCallback as it caches custom views which must be reloaded - // with the classloader of the new LayoutLib. We also have to clear it out - // because it stores a reference to the layout library which could have changed. - mProjectCallback = null; - - // FIXME: get rid of the current LayoutScene if any. - } - - private class ReloadListener implements ILayoutReloadListener { - /** - * Called when the file changes triggered a redraw of the layout - */ - @Override - public void reloadLayout(final ChangeFlags flags, final boolean libraryChanged) { - if (mConfigChooser.isDisposed()) { - return; - } - Display display = mConfigChooser.getDisplay(); - display.asyncExec(new Runnable() { - @Override - public void run() { - reloadLayoutSwt(flags, libraryChanged); - } - }); - } - - /** Reload layout. <b>Must be called on the SWT thread</b> */ - private void reloadLayoutSwt(ChangeFlags flags, boolean libraryChanged) { - if (mConfigChooser.isDisposed()) { - return; - } - assert mConfigChooser.getDisplay().getThread() == Thread.currentThread(); - - boolean recompute = false; - // we only care about the r class of the main project. - if (flags.rClass && libraryChanged == false) { - recompute = true; - if (mEditedFile != null) { - ResourceManager manager = ResourceManager.getInstance(); - ProjectResources projectRes = manager.getProjectResources( - mEditedFile.getProject()); - - if (projectRes != null) { - projectRes.resetDynamicIds(); - } - } - } - - if (flags.localeList) { - // the locale list *potentially* changed so we update the locale in the - // config composite. - // However there's no recompute, as it could not be needed - // (for instance a new layout) - // If a resource that's not a layout changed this will trigger a recompute anyway. - mConfigChooser.updateLocales(); - } - - // if a resources was modified. - if (flags.resources) { - recompute = true; - - // TODO: differentiate between single and multi resource file changed, and whether - // the resource change affects the cache. - - // force a reparse in case a value XML file changed. - mConfiguredProjectRes = null; - mResourceResolver = null; - - // clear the cache in the bridge in case a bitmap/9-patch changed. - LayoutLibrary layoutLib = getReadyLayoutLib(true /*displayError*/); - if (layoutLib != null) { - layoutLib.clearCaches(mEditedFile.getProject()); - } - } - - if (flags.code) { - // only recompute if the custom view loader was used to load some code. - if (mProjectCallback != null && mProjectCallback.isUsed()) { - mProjectCallback = null; - recompute = true; - } - } - - if (flags.manifest) { - recompute |= computeSdkVersion(); - } - - if (recompute) { - if (mEditorDelegate.isGraphicalEditorActive()) { - recomputeLayout(); - } else { - mNeedsRecompute = true; - } - } - } - } - - // ---- Error handling ---- - - /** - * Switches the sash to display the error label. - * - * @param errorFormat The new error to display if not null. - * @param parameters String.format parameters for the error format. - */ - private void displayError(String errorFormat, Object...parameters) { - if (errorFormat != null) { - mErrorLabel.setText(String.format(errorFormat, parameters)); - } else { - mErrorLabel.setText(""); - } - mSashError.setMaximizedControl(null); - } - - /** Displays the canvas and hides the error label. */ - private void hideError() { - mErrorLabel.setText(""); - mSashError.setMaximizedControl(mCanvasViewer.getControl()); - } - - /** Display the problem list encountered during a render */ - private void displayUserStackTrace(RenderLogger logger, boolean append) { - List<Throwable> throwables = logger.getFirstTrace(); - if (throwables == null || throwables.isEmpty()) { - return; - } - - Throwable throwable = throwables.get(0); - - if (throwable instanceof RenderSecurityException) { - addActionLink(mErrorLabel, ActionLinkStyleRange.LINK_DISABLE_SANDBOX, - "\nTurn off custom view rendering sandbox\n"); - - StringBuilder builder = new StringBuilder(200); - String lastFailedPath = RenderSecurityManager.getLastFailedPath(); - if (lastFailedPath != null) { - builder.append("Diagnostic info for ADT bug report:\n"); - builder.append("Failed path: ").append(lastFailedPath).append('\n'); - String tempDir = System.getProperty("java.io.tmpdir"); - builder.append("Normal temp dir: ").append(tempDir).append('\n'); - File normalized = new File(tempDir); - builder.append("Normalized temp dir: ").append(normalized.getPath()).append('\n'); - try { - builder.append("Canonical temp dir: ").append(normalized.getCanonicalPath()) - .append('\n'); - } catch (IOException e) { - // ignore - } - builder.append("os.name: ").append(System.getProperty("os.name")).append('\n'); - builder.append("os.version: ").append(System.getProperty("os.version")); - builder.append('\n'); - builder.append("java.runtime.version: "); - builder.append(System.getProperty("java.runtime.version")); - } - if (throwable.getMessage().equals("Unable to create temporary file")) { - String javaVersion = System.getProperty("java.version"); - if (javaVersion.startsWith("1.7.0_")) { - int version = Integer - .parseInt(javaVersion.substring(javaVersion.indexOf('_') + 1)); - if (version > 0 && version < 45) { - builder.append('\n'); - builder.append("Tip: This may be caused by using an older version " + - "of JDK 1.7.0; try using at least 1.7.0_45 (you are using " + - javaVersion + ")"); - } - } - } - if (builder.length() > 0) { - addText(mErrorLabel, builder.toString()); - } - } - - StackTraceElement[] frames = throwable.getStackTrace(); - int end = -1; - boolean haveInterestingFrame = false; - for (int i = 0; i < frames.length; i++) { - StackTraceElement frame = frames[i]; - if (isInterestingFrame(frame)) { - haveInterestingFrame = true; - } - String className = frame.getClassName(); - if (className.equals( - "com.android.layoutlib.bridge.impl.RenderSessionImpl")) { //$NON-NLS-1$ - end = i; - break; - } - } - - if (end == -1 || !haveInterestingFrame) { - // Not a recognized stack trace range: just skip it - return; - } - - if (!append) { - mErrorLabel.setText("\n"); //$NON-NLS-1$ - } else { - addText(mErrorLabel, "\n\n"); //$NON-NLS-1$ - } - - addText(mErrorLabel, throwable.toString() + '\n'); - for (int i = 0; i < end; i++) { - StackTraceElement frame = frames[i]; - String className = frame.getClassName(); - String methodName = frame.getMethodName(); - addText(mErrorLabel, " at " + className + '.' + methodName + '('); - String fileName = frame.getFileName(); - if (fileName != null && !fileName.isEmpty()) { - int lineNumber = frame.getLineNumber(); - String location = fileName + ':' + lineNumber; - if (isInterestingFrame(frame)) { - addActionLink(mErrorLabel, ActionLinkStyleRange.LINK_OPEN_LINE, - location, className, methodName, fileName, lineNumber); - } else { - addText(mErrorLabel, location); - } - addText(mErrorLabel, ")\n"); //$NON-NLS-1$ - } - } - } - - private static boolean isInterestingFrame(StackTraceElement frame) { - String className = frame.getClassName(); - return !(className.startsWith("android.") //$NON-NLS-1$ - || className.startsWith("com.android.") //$NON-NLS-1$ - || className.startsWith("java.") //$NON-NLS-1$ - || className.startsWith("javax.") //$NON-NLS-1$ - || className.startsWith("sun.")); //$NON-NLS-1$ - } - - /** - * Switches the sash to display the error label to show a list of - * missing classes and give options to create them. - */ - private void displayFailingClasses(Set<String> missingClasses, Set<String> brokenClasses, - boolean append) { - if (missingClasses.size() == 0 && brokenClasses.size() == 0) { - return; - } - - if (!append) { - mErrorLabel.setText(""); //$NON-NLS-1$ - } else { - addText(mErrorLabel, "\n"); //$NON-NLS-1$ - } - - if (missingClasses.size() > 0) { - addText(mErrorLabel, "The following classes could not be found:\n"); - for (String clazz : missingClasses) { - addText(mErrorLabel, "- "); - addText(mErrorLabel, clazz); - addText(mErrorLabel, " ("); - - IProject project = getProject(); - Collection<String> customViews = getCustomViewClassNames(project); - addTypoSuggestions(clazz, customViews, false); - addTypoSuggestions(clazz, customViews, true); - addTypoSuggestions(clazz, getAndroidViewClassNames(project), false); - - addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_FIX_BUILD_PATH, "Fix Build Path", clazz); - addText(mErrorLabel, ", "); - addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_EDIT_XML, "Edit XML", clazz); - if (clazz.indexOf('.') != -1) { - // Add "Create Class" link, but only for custom views - addText(mErrorLabel, ", "); - addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_CREATE_CLASS, "Create Class", clazz); - } - addText(mErrorLabel, ")\n"); - } - } - if (brokenClasses.size() > 0) { - addText(mErrorLabel, "The following classes could not be instantiated:\n"); - - // Do we have a custom class (not an Android or add-ons class) - boolean haveCustomClass = false; - - for (String clazz : brokenClasses) { - addText(mErrorLabel, "- "); - addText(mErrorLabel, clazz); - addText(mErrorLabel, " ("); - addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_OPEN_CLASS, "Open Class", clazz); - addText(mErrorLabel, ", "); - addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_SHOW_LOG, "Show Error Log", clazz); - addText(mErrorLabel, ")\n"); - - if (!(clazz.startsWith("android.") || //$NON-NLS-1$ - clazz.startsWith("com.google."))) { //$NON-NLS-1$ - haveCustomClass = true; - } - } - - addText(mErrorLabel, "See the Error Log (Window > Show View) for more details.\n"); - - if (haveCustomClass) { - addBoldText(mErrorLabel, "Tip: Use View.isInEditMode() in your custom views " - + "to skip code when shown in Eclipse"); - } - } - - mSashError.setMaximizedControl(null); - } - - private void addTypoSuggestions(String actual, Collection<String> views, - boolean compareWithPackage) { - if (views.size() == 0) { - return; - } - - // Look for typos and try to match with custom views and android views - String actualBase = actual.substring(actual.lastIndexOf('.') + 1); - int maxDistance = actualBase.length() >= 4 ? 2 : 1; - - if (views.size() > 0) { - for (String suggested : views) { - String suggestedBase = suggested.substring(suggested.lastIndexOf('.') + 1); - - String matchWith = compareWithPackage ? suggested : suggestedBase; - if (Math.abs(actualBase.length() - matchWith.length()) > maxDistance) { - // The string lengths differ more than the allowed edit distance; - // no point in even attempting to compute the edit distance (requires - // O(n*m) storage and O(n*m) speed, where n and m are the string lengths) - continue; - } - if (LintUtils.editDistance(actualBase, matchWith) <= maxDistance) { - // Suggest this class as a typo for the given class - String labelClass = (suggestedBase.equals(actual) || actual.indexOf('.') != -1) - ? suggested : suggestedBase; - addActionLink(mErrorLabel, - ActionLinkStyleRange.LINK_CHANGE_CLASS_TO, - String.format("Change to %1$s", - // Only show full package name if class name - // is the same - labelClass), - actual, - viewNeedsPackage(suggested) ? suggested : suggestedBase); - addText(mErrorLabel, ", "); - } - } - } - } - - private static Collection<String> getCustomViewClassNames(IProject project) { - CustomViewFinder finder = CustomViewFinder.get(project); - Collection<String> views = finder.getAllViews(); - if (views == null) { - finder.refresh(); - views = finder.getAllViews(); - } - - return views; - } - - private static Collection<String> getAndroidViewClassNames(IProject project) { - Sdk currentSdk = Sdk.getCurrent(); - IAndroidTarget target = currentSdk.getTarget(project); - if (target != null) { - AndroidTargetData targetData = currentSdk.getTargetData(target); - if (targetData != null) { - LayoutDescriptors layoutDescriptors = targetData.getLayoutDescriptors(); - return layoutDescriptors.getAllViewClassNames(); - } - } - - return Collections.emptyList(); - } - - /** Add a normal line of text to the styled text widget. */ - private void addText(StyledText styledText, String...string) { - for (String s : string) { - styledText.append(s); - } - } - - /** Display the problem list encountered during a render */ - private void displayLoggerProblems(IProject project, RenderLogger logger) { - if (logger.hasProblems()) { - mErrorLabel.setText(""); - // A common source of problems is attempting to open a layout when there are - // compilation errors. In this case, may not have run (or may not be up to date) - // so resources cannot be looked up etc. Explain this situation to the user. - - boolean hasAaptErrors = false; - boolean hasJavaErrors = false; - try { - IMarker[] markers; - markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); - if (markers.length > 0) { - for (IMarker marker : markers) { - String markerType = marker.getType(); - if (markerType.equals(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER)) { - int severity = marker.getAttribute(IMarker.SEVERITY, -1); - if (severity == IMarker.SEVERITY_ERROR) { - hasJavaErrors = true; - } - } else if (markerType.equals(AdtConstants.MARKER_AAPT_COMPILE)) { - int severity = marker.getAttribute(IMarker.SEVERITY, -1); - if (severity == IMarker.SEVERITY_ERROR) { - hasAaptErrors = true; - } - } - } - } - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - - if (logger.seenTagPrefix(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR)) { - addBoldText(mErrorLabel, - "Missing styles. Is the correct theme chosen for this layout?\n"); - addText(mErrorLabel, - "Use the Theme combo box above the layout to choose a different layout, " + - "or fix the theme style references.\n\n"); - } - - List<Throwable> trace = logger.getFirstTrace(); - if (trace != null - && trace.toString().contains( - "java.lang.IndexOutOfBoundsException: Index: 2, Size: 2") //$NON-NLS-1$ - && mConfigChooser.getConfiguration().getDensity() == Density.TV) { - addBoldText(mErrorLabel, - "It looks like you are using a render target where the layout library " + - "does not support the tvdpi density.\n\n"); - addText(mErrorLabel, "Please try either updating to " + - "the latest available version (using the SDK manager), or if no updated " + - "version is available for this specific version of Android, try using " + - "a more recent render target version.\n\n"); - - } - - if (hasAaptErrors && logger.seenTagPrefix(LayoutLog.TAG_RESOURCES_PREFIX)) { - // Text will automatically be wrapped by the error widget so no reason - // to insert linebreaks in this error message: - String message = - "NOTE: This project contains resource errors, so aapt did not succeed, " - + "which can cause rendering failures. " - + "Fix resource problems first.\n\n"; - addBoldText(mErrorLabel, message); - } else if (hasJavaErrors && mProjectCallback != null && mProjectCallback.isUsed()) { - // Text will automatically be wrapped by the error widget so no reason - // to insert linebreaks in this error message: - String message = - "NOTE: This project contains Java compilation errors, " - + "which can cause rendering failures for custom views. " - + "Fix compilation problems first.\n\n"; - addBoldText(mErrorLabel, message); - } - - if (logger.seenTag(RenderLogger.TAG_MISSING_DIMENSION)) { - List<UiElementNode> elements = UiDocumentNode.getAllElements(getModel()); - for (UiElementNode element : elements) { - String width = element.getAttributeValue(ATTR_LAYOUT_WIDTH); - if (width == null || width.length() == 0) { - addSetAttributeLink(element, ATTR_LAYOUT_WIDTH); - } - - String height = element.getAttributeValue(ATTR_LAYOUT_HEIGHT); - if (height == null || height.length() == 0) { - addSetAttributeLink(element, ATTR_LAYOUT_HEIGHT); - } - } - } - - String problems = logger.getProblems(false /*includeFidelityWarnings*/); - addText(mErrorLabel, problems); - - List<String> fidelityWarnings = logger.getFidelityWarnings(); - if (fidelityWarnings != null && fidelityWarnings.size() > 0) { - addText(mErrorLabel, - "The graphics preview in the layout editor may not be accurate:\n"); - for (String warning : fidelityWarnings) { - addText(mErrorLabel, warning + ' '); - addActionLink(mErrorLabel, - ActionLinkStyleRange.IGNORE_FIDELITY_WARNING, - "(Ignore for this session)\n", warning); - } - } - - mSashError.setMaximizedControl(null); - } else { - mSashError.setMaximizedControl(mCanvasViewer.getControl()); - } - } - - /** Appends an action link to set the given attribute on the given value */ - private void addSetAttributeLink(UiElementNode element, String attribute) { - if (element.getXmlNode().getNodeName().equals(GRID_LAYOUT)) { - // GridLayout does not require a layout_width or layout_height to be defined - return; - } - - String fill = VALUE_FILL_PARENT; - // See whether we should offer match_parent instead of fill_parent - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk != null) { - IAndroidTarget target = currentSdk.getTarget(getProject()); - if (target.getVersion().getApiLevel() >= 8) { - fill = VALUE_MATCH_PARENT; - } - } - - String id = element.getAttributeValue(ATTR_ID); - if (id == null || id.length() == 0) { - id = '<' + element.getXmlNode().getNodeName() + '>'; - } else { - id = BaseLayoutRule.stripIdPrefix(id); - } - - addText(mErrorLabel, String.format("\"%1$s\" does not set the required %2$s attribute:\n", - id, attribute)); - addText(mErrorLabel, " (1) "); - addActionLink(mErrorLabel, - ActionLinkStyleRange.SET_ATTRIBUTE, - String.format("Set to \"%1$s\"", VALUE_WRAP_CONTENT), - element, attribute, VALUE_WRAP_CONTENT); - addText(mErrorLabel, "\n (2) "); - addActionLink(mErrorLabel, - ActionLinkStyleRange.SET_ATTRIBUTE, - String.format("Set to \"%1$s\"\n", fill), - element, attribute, fill); - } - - /** Appends the given text as a bold string in the given text widget */ - private void addBoldText(StyledText styledText, String text) { - String s = styledText.getText(); - int start = (s == null ? 0 : s.length()); - - styledText.append(text); - StyleRange sr = new StyleRange(); - sr.start = start; - sr.length = text.length(); - sr.fontStyle = SWT.BOLD; - styledText.setStyleRange(sr); - } - - /** - * Add a URL-looking link to the styled text widget. - * <p/> - * A mouse-click listener is setup and it interprets the link based on the - * action, corresponding to the value fields in {@link ActionLinkStyleRange}. - */ - private void addActionLink(StyledText styledText, int action, String label, - Object... data) { - String s = styledText.getText(); - int start = (s == null ? 0 : s.length()); - styledText.append(label); - - StyleRange sr = new ActionLinkStyleRange(action, data); - sr.start = start; - sr.length = label.length(); - sr.fontStyle = SWT.NORMAL; - sr.underlineStyle = SWT.UNDERLINE_LINK; - sr.underline = true; - styledText.setStyleRange(sr); - } - - /** - * Looks up the resource file corresponding to the given type - * - * @param type The type of resource to look up, such as {@link ResourceType#LAYOUT} - * @param name The name of the resource (not including ".xml") - * @param isFrameworkResource if true, the resource is a framework resource, otherwise - * it's a project resource - * @return the resource file defining the named resource, or null if not found - */ - public IPath findResourceFile(ResourceType type, String name, boolean isFrameworkResource) { - // FIXME: This code does not handle theme value resolution. - // There is code to handle this, but it's in layoutlib; we should - // expose that and use it here. - - Map<ResourceType, Map<String, ResourceValue>> map; - map = isFrameworkResource ? mConfiguredFrameworkRes : mConfiguredProjectRes; - if (map == null) { - // Not yet configured - return null; - } - - Map<String, ResourceValue> layoutMap = map.get(type); - if (layoutMap != null) { - ResourceValue value = layoutMap.get(name); - if (value != null) { - String valueStr = value.getValue(); - if (valueStr.startsWith("?")) { //$NON-NLS-1$ - // FIXME: It's a reference. We should resolve this properly. - return null; - } - return new Path(valueStr); - } - } - - return null; - } - - /** - * Looks up the path to the file corresponding to the given attribute value, such as - * @layout/foo, which will return the foo.xml file in res/layout/. (The general format - * of the resource url is {@literal @[<package_name>:]<resource_type>/<resource_name>}. - * - * @param url the attribute url - * @return the path to the file defining this attribute, or null if not found - */ - public IPath findResourceFile(String url) { - if (!url.startsWith("@")) { //$NON-NLS-1$ - return null; - } - int typeEnd = url.indexOf('/', 1); - if (typeEnd == -1) { - return null; - } - int nameBegin = typeEnd + 1; - int typeBegin = 1; - int colon = url.lastIndexOf(':', typeEnd); - boolean isFrameworkResource = false; - if (colon != -1) { - // The URL contains a package name. - // While the url format technically allows other package names, - // the platform apparently only supports @android for now (or if it does, - // there are no usages in the current code base so this is not common). - String packageName = url.substring(typeBegin, colon); - if (ANDROID_PKG.equals(packageName)) { - isFrameworkResource = true; - } - - typeBegin = colon + 1; - } - - String typeName = url.substring(typeBegin, typeEnd); - ResourceType type = ResourceType.getEnum(typeName); - if (type == null) { - return null; - } - - String name = url.substring(nameBegin); - return findResourceFile(type, name, isFrameworkResource); - } - - /** - * Resolve the given @string reference into a literal String using the current project - * configuration - * - * @param text the text resource reference to resolve - * @return the resolved string, or null - */ - public String findString(String text) { - if (text.startsWith(STRING_PREFIX)) { - return findString(text.substring(STRING_PREFIX.length()), false); - } else if (text.startsWith(ANDROID_STRING_PREFIX)) { - return findString(text.substring(ANDROID_STRING_PREFIX.length()), true); - } else { - return text; - } - } - - private String findString(String name, boolean isFrameworkResource) { - Map<ResourceType, Map<String, ResourceValue>> map; - map = isFrameworkResource ? mConfiguredFrameworkRes : mConfiguredProjectRes; - if (map == null) { - // Not yet configured - return null; - } - - Map<String, ResourceValue> layoutMap = map.get(ResourceType.STRING); - if (layoutMap != null) { - ResourceValue value = layoutMap.get(name); - if (value != null) { - // FIXME: This code does not handle theme value resolution. - // There is code to handle this, but it's in layoutlib; we should - // expose that and use it here. - return value.getValue(); - } - } - - return null; - } - - /** - * This StyleRange represents a clickable link in the render output, where various - * actions can be taken such as creating a class, opening the project chooser to - * adjust the build path, etc. - */ - private class ActionLinkStyleRange extends StyleRange { - /** Create a view class */ - private static final int LINK_CREATE_CLASS = 1; - /** Edit the build path for the current project */ - private static final int LINK_FIX_BUILD_PATH = 2; - /** Show the XML tab */ - private static final int LINK_EDIT_XML = 3; - /** Open the given class */ - private static final int LINK_OPEN_CLASS = 4; - /** Show the error log */ - private static final int LINK_SHOW_LOG = 5; - /** Change the class reference to the given fully qualified name */ - private static final int LINK_CHANGE_CLASS_TO = 6; - /** Ignore the given fidelity warning */ - private static final int IGNORE_FIDELITY_WARNING = 7; - /** Set an attribute on the given XML element to a given value */ - private static final int SET_ATTRIBUTE = 8; - /** Open the given file and line number */ - private static final int LINK_OPEN_LINE = 9; - /** Disable sandbox */ - private static final int LINK_DISABLE_SANDBOX = 10; - - /** Client data: the contents depend on the specific action */ - private final Object[] mData; - /** The action to be taken when the link is clicked */ - private final int mAction; - - private ActionLinkStyleRange(int action, Object... data) { - super(); - mAction = action; - mData = data; - } - - /** Performs the click action */ - public void onClick() { - switch (mAction) { - case LINK_CREATE_CLASS: - createNewClass((String) mData[0]); - break; - case LINK_EDIT_XML: - mEditorDelegate.getEditor().setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID); - break; - case LINK_FIX_BUILD_PATH: - @SuppressWarnings("restriction") - String id = BuildPathsPropertyPage.PROP_ID; - PreferencesUtil.createPropertyDialogOn( - AdtPlugin.getShell(), - getProject(), id, null, null).open(); - break; - case LINK_OPEN_CLASS: - AdtPlugin.openJavaClass(getProject(), (String) mData[0]); - break; - case LINK_OPEN_LINE: - boolean success = AdtPlugin.openStackTraceLine( - (String) mData[0], // class - (String) mData[1], // method - (String) mData[2], // file - (Integer) mData[3]); // line - if (!success) { - MessageDialog.openError(mErrorLabel.getShell(), "Not Found", - String.format("Could not find %1$s.%2$s", mData[0], mData[1])); - } - break; - case LINK_SHOW_LOG: - IWorkbench workbench = PlatformUI.getWorkbench(); - IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow(); - try { - IWorkbenchPage page = workbenchWindow.getActivePage(); - page.showView("org.eclipse.pde.runtime.LogView"); //$NON-NLS-1$ - } catch (PartInitException e) { - AdtPlugin.log(e, null); - } - break; - case LINK_CHANGE_CLASS_TO: - // Change class reference of mData[0] to mData[1] - // TODO: run under undo lock - MultiTextEdit edits = new MultiTextEdit(); - ISourceViewer textViewer = - mEditorDelegate.getEditor().getStructuredSourceViewer(); - IDocument document = textViewer.getDocument(); - String xml = document.get(); - int index = 0; - // Replace <old with <new and </old with </new - String prefix = "<"; //$NON-NLS-1$ - String find = prefix + mData[0]; - String replaceWith = prefix + mData[1]; - while (true) { - index = xml.indexOf(find, index); - if (index == -1) { - break; - } - edits.addChild(new ReplaceEdit(index, find.length(), replaceWith)); - index += find.length(); - } - index = 0; - prefix = "</"; //$NON-NLS-1$ - find = prefix + mData[0]; - replaceWith = prefix + mData[1]; - while (true) { - index = xml.indexOf(find, index); - if (index == -1) { - break; - } - edits.addChild(new ReplaceEdit(index, find.length(), replaceWith)); - index += find.length(); - } - // Handle <view class="old"> - index = 0; - prefix = "\""; //$NON-NLS-1$ - String suffix = "\""; //$NON-NLS-1$ - find = prefix + mData[0] + suffix; - replaceWith = prefix + mData[1] + suffix; - while (true) { - index = xml.indexOf(find, index); - if (index == -1) { - break; - } - edits.addChild(new ReplaceEdit(index, find.length(), replaceWith)); - index += find.length(); - } - try { - edits.apply(document); - } catch (MalformedTreeException e) { - AdtPlugin.log(e, null); - } catch (BadLocationException e) { - AdtPlugin.log(e, null); - } - break; - case IGNORE_FIDELITY_WARNING: - RenderLogger.ignoreFidelityWarning((String) mData[0]); - recomputeLayout(); - break; - case SET_ATTRIBUTE: { - final UiElementNode element = (UiElementNode) mData[0]; - final String attribute = (String) mData[1]; - final String value = (String) mData[2]; - mEditorDelegate.getEditor().wrapUndoEditXmlModel( - String.format("Set \"%1$s\" to \"%2$s\"", attribute, value), - new Runnable() { - @Override - public void run() { - element.setAttributeValue(attribute, ANDROID_URI, value, true); - element.commitDirtyAttributesToXml(); - } - }); - break; - } - case LINK_DISABLE_SANDBOX: { - RenderSecurityManager.sEnabled = false; - recomputeLayout(); - - MessageDialog.openInformation(AdtPlugin.getShell(), - "Disabled Rendering Sandbox", - "The custom view rendering sandbox was disabled for this session.\n\n" + - "You can turn it off permanently by adding\n" + - "-D" + ENABLED_PROPERTY + "=" + VALUE_FALSE + "\n" + - "as a new line in eclipse.ini."); - - break; - } - default: - assert false : mAction; - break; - } - } - - @Override - public boolean similarTo(StyleRange style) { - // Prevent adjacent link ranges from getting merged - return false; - } - } - - /** - * Returns the error label for the graphical editor (which may not be visible - * or showing errors) - * - * @return the error label, never null - */ - StyledText getErrorLabel() { - return mErrorLabel; - } - - /** - * Monitor clicks on the error label. - * If the click happens on a style range created by - * {@link GraphicalEditorPart#addClassLink(StyledText, String)}, we assume it's about - * a missing class and we then proceed to display the standard Eclipse class creator wizard. - */ - private class ErrorLabelListener extends MouseAdapter { - - @Override - public void mouseUp(MouseEvent event) { - super.mouseUp(event); - - if (event.widget != mErrorLabel) { - return; - } - - int offset = mErrorLabel.getCaretOffset(); - - StyleRange r = null; - StyleRange[] ranges = mErrorLabel.getStyleRanges(); - if (ranges != null && ranges.length > 0) { - for (StyleRange sr : ranges) { - if (sr.start <= offset && sr.start + sr.length > offset) { - r = sr; - break; - } - } - } - - if (r instanceof ActionLinkStyleRange) { - ActionLinkStyleRange range = (ActionLinkStyleRange) r; - range.onClick(); - } - - LayoutCanvas canvas = getCanvasControl(); - canvas.updateMenuActionState(); - } - } - - private void createNewClass(String fqcn) { - - int pos = fqcn.lastIndexOf('.'); - String packageName = pos < 0 ? "" : fqcn.substring(0, pos); //$NON-NLS-1$ - String className = pos <= 0 || pos >= fqcn.length() ? "" : fqcn.substring(pos + 1); //$NON-NLS-1$ - - // create the wizard page for the class creation, and configure it - NewClassWizardPage page = new NewClassWizardPage(); - - // set the parent class - page.setSuperClass(SdkConstants.CLASS_VIEW, true /* canBeModified */); - - // get the source folders as java elements. - IPackageFragmentRoot[] roots = getPackageFragmentRoots( - mEditorDelegate.getEditor().getProject(), - false /*includeContainers*/, true /*skipGenFolder*/); - - IPackageFragmentRoot currentRoot = null; - IPackageFragment currentFragment = null; - int packageMatchCount = -1; - - for (IPackageFragmentRoot root : roots) { - // Get the java element for the package. - // This method is said to always return a IPackageFragment even if the - // underlying folder doesn't exist... - IPackageFragment fragment = root.getPackageFragment(packageName); - if (fragment != null && fragment.exists()) { - // we have a perfect match! we use it. - currentRoot = root; - currentFragment = fragment; - packageMatchCount = -1; - break; - } else { - // we don't have a match. we look for the fragment with the best match - // (ie the closest parent package we can find) - try { - IJavaElement[] children; - children = root.getChildren(); - for (IJavaElement child : children) { - if (child instanceof IPackageFragment) { - fragment = (IPackageFragment)child; - if (packageName.startsWith(fragment.getElementName())) { - // its a match. get the number of segments - String[] segments = fragment.getElementName().split("\\."); //$NON-NLS-1$ - if (segments.length > packageMatchCount) { - packageMatchCount = segments.length; - currentFragment = fragment; - currentRoot = root; - } - } - } - } - } catch (JavaModelException e) { - // Couldn't get the children: we just ignore this package root. - } - } - } - - ArrayList<IPackageFragment> createdFragments = null; - - if (currentRoot != null) { - // if we have a perfect match, we set it and we're done. - if (packageMatchCount == -1) { - page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/); - page.setPackageFragment(currentFragment, true /* canBeModified */); - } else { - // we have a partial match. - // create the package. We have to start with the first segment so that we - // know what to delete in case of a cancel. - try { - createdFragments = new ArrayList<IPackageFragment>(); - - int totalCount = packageName.split("\\.").length; //$NON-NLS-1$ - int count = 0; - int index = -1; - // skip the matching packages - while (count < packageMatchCount) { - index = packageName.indexOf('.', index+1); - count++; - } - - // create the rest of the segments, except for the last one as indexOf will - // return -1; - while (count < totalCount - 1) { - index = packageName.indexOf('.', index+1); - count++; - createdFragments.add(currentRoot.createPackageFragment( - packageName.substring(0, index), - true /* force*/, new NullProgressMonitor())); - } - - // create the last package - createdFragments.add(currentRoot.createPackageFragment( - packageName, true /* force*/, new NullProgressMonitor())); - - // set the root and fragment in the Wizard page - page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/); - page.setPackageFragment(createdFragments.get(createdFragments.size()-1), - true /* canBeModified */); - } catch (JavaModelException e) { - // If we can't create the packages, there's a problem. - // We revert to the default package - for (IPackageFragmentRoot root : roots) { - // Get the java element for the package. - // This method is said to always return a IPackageFragment even if the - // underlying folder doesn't exist... - IPackageFragment fragment = root.getPackageFragment(packageName); - if (fragment != null && fragment.exists()) { - page.setPackageFragmentRoot(root, true /* canBeModified*/); - page.setPackageFragment(fragment, true /* canBeModified */); - break; - } - } - } - } - } else if (roots.length > 0) { - // if we haven't found a valid fragment, we set the root to the first source folder. - page.setPackageFragmentRoot(roots[0], true /* canBeModified*/); - } - - // if we have a starting class name we use it - if (className != null) { - page.setTypeName(className, true /* canBeModified*/); - } - - // create the action that will open it the wizard. - OpenNewClassWizardAction action = new OpenNewClassWizardAction(); - action.setConfiguredWizardPage(page); - action.run(); - IJavaElement element = action.getCreatedElement(); - - if (element == null) { - // lets delete the packages we created just for this. - // we need to start with the leaf and go up - if (createdFragments != null) { - try { - for (int i = createdFragments.size() - 1 ; i >= 0 ; i--) { - createdFragments.get(i).delete(true /* force*/, - new NullProgressMonitor()); - } - } catch (JavaModelException e) { - e.printStackTrace(); - } - } - } - } - - /** - * Computes and return the {@link IPackageFragmentRoot}s corresponding to the source - * folders of the specified project. - * - * @param project the project - * @param includeContainers True to include containers - * @param skipGenFolder True to skip the "gen" folder - * @return an array of IPackageFragmentRoot. - */ - private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project, - boolean includeContainers, boolean skipGenFolder) { - ArrayList<IPackageFragmentRoot> result = new ArrayList<IPackageFragmentRoot>(); - try { - IJavaProject javaProject = JavaCore.create(project); - IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); - for (int i = 0; i < roots.length; i++) { - if (skipGenFolder) { - IResource resource = roots[i].getResource(); - if (resource != null && resource.getName().equals(FD_GEN_SOURCES)) { - continue; - } - } - IClasspathEntry entry = roots[i].getRawClasspathEntry(); - if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE || - (includeContainers && - entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER)) { - result.add(roots[i]); - } - } - } catch (JavaModelException e) { - } - - return result.toArray(new IPackageFragmentRoot[result.size()]); - } - - /** - * Reopens this file as included within the given file (this assumes that the given - * file has an include tag referencing this view, and the set of views that have this - * property can be found using the {@link IncludeFinder}. - * - * @param includeWithin reference to a file to include as a surrounding context, - * or null to show the file standalone - */ - public void showIn(Reference includeWithin) { - mIncludedWithin = includeWithin; - - if (includeWithin != null) { - IFile file = includeWithin.getFile(); - - // Update configuration - if (file != null) { - mConfigChooser.resetConfigFor(file); - } - } - recomputeLayout(); - } - - /** - * Return all resource names of a given type, either in the project or in the - * framework. - * - * @param framework if true, return all the framework resource names, otherwise return - * all the project resource names - * @param type the type of resource to look up - * @return a collection of resource names, never null but possibly empty - */ - public Collection<String> getResourceNames(boolean framework, ResourceType type) { - Map<ResourceType, Map<String, ResourceValue>> map = - framework ? mConfiguredFrameworkRes : mConfiguredProjectRes; - Map<String, ResourceValue> animations = map.get(type); - if (animations != null) { - return animations.keySet(); - } else { - return Collections.emptyList(); - } - } - - /** - * Return this editor's current configuration - * - * @return the current configuration - */ - public FolderConfiguration getConfiguration() { - return mConfigChooser.getConfiguration().getFullConfig(); - } - - /** - * Figures out the project's minSdkVersion and targetSdkVersion and return whether the values - * have changed. - */ - private boolean computeSdkVersion() { - int oldMinSdkVersion = mMinSdkVersion; - int oldTargetSdkVersion = mTargetSdkVersion; - - Pair<Integer, Integer> v = ManifestInfo.computeSdkVersions(mEditedFile.getProject()); - mMinSdkVersion = v.getFirst(); - mTargetSdkVersion = v.getSecond(); - - return oldMinSdkVersion != mMinSdkVersion || oldTargetSdkVersion != mTargetSdkVersion; - } - - /** - * Returns the associated configuration chooser - * - * @return the configuration chooser - */ - @NonNull - public ConfigurationChooser getConfigurationChooser() { - return mConfigChooser; - } - - /** - * Returns the associated layout actions bar - * - * @return the layout actions bar - */ - @NonNull - public LayoutActionBar getLayoutActionBar() { - return mActionBar; - } - - /** - * Returns the target SDK version - * - * @return the target SDK version - */ - public int getTargetSdkVersion() { - return mTargetSdkVersion; - } - - /** - * Returns the minimum SDK version - * - * @return the minimum SDK version - */ - public int getMinSdkVersion() { - return mMinSdkVersion; - } - - /** If the flyout hover is showing, dismiss it */ - public void dismissHoverPalette() { - mPaletteComposite.dismissHover(); - } - - // ---- Implements IFlyoutListener ---- - - @Override - public void stateChanged(int oldState, int newState) { - // Auto zoom the surface if you open or close flyout windows such as the palette - // or the property/outline views - if (newState == STATE_OPEN || newState == STATE_COLLAPSED && oldState == STATE_OPEN) { - getCanvasControl().setFitScale(true /*onlyZoomOut*/, true /*allowZoomIn*/); - } - - sDockingStateVersion++; - mDockingStateVersion = sDockingStateVersion; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/HoverOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/HoverOverlay.java deleted file mode 100644 index 2e7c559db..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/HoverOverlay.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtDrawingStyle.HOVER; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtDrawingStyle.HOVER_SELECTION; - -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Rectangle; - -import java.util.List; - -/** - * The {@link HoverOverlay} paints an optional hover on top of the layout, - * highlighting the currently hovered view. - */ -public class HoverOverlay extends Overlay { - private final LayoutCanvas mCanvas; - - /** Hover border color. Must be disposed, it's NOT a system color. */ - private Color mHoverStrokeColor; - - /** Hover fill color. Must be disposed, it's NOT a system color. */ - private Color mHoverFillColor; - - /** Hover border select color. Must be disposed, it's NOT a system color. */ - private Color mHoverSelectStrokeColor; - - /** Hover fill select color. Must be disposed, it's NOT a system color. */ - private Color mHoverSelectFillColor; - - /** Vertical scaling & scrollbar information. */ - private CanvasTransform mVScale; - - /** Horizontal scaling & scrollbar information. */ - private CanvasTransform mHScale; - - /** - * Current mouse hover border rectangle. Null when there's no mouse hover. - * The rectangle coordinates do not take account of the translation, which - * must be applied to the rectangle when drawing. - */ - private Rectangle mHoverRect; - - /** - * Constructs a new {@link HoverOverlay} linked to the given view hierarchy. - * - * @param canvas the associated canvas - * @param hScale The {@link CanvasTransform} to use to transfer horizontal layout - * coordinates to screen coordinates. - * @param vScale The {@link CanvasTransform} to use to transfer vertical layout - * coordinates to screen coordinates. - */ - public HoverOverlay(LayoutCanvas canvas, CanvasTransform hScale, CanvasTransform vScale) { - mCanvas = canvas; - mHScale = hScale; - mVScale = vScale; - } - - @Override - public void create(Device device) { - if (SwtDrawingStyle.HOVER.getStrokeColor() != null) { - mHoverStrokeColor = new Color(device, SwtDrawingStyle.HOVER.getStrokeColor()); - } - if (SwtDrawingStyle.HOVER.getFillColor() != null) { - mHoverFillColor = new Color(device, SwtDrawingStyle.HOVER.getFillColor()); - } - - if (SwtDrawingStyle.HOVER_SELECTION.getStrokeColor() != null) { - mHoverSelectStrokeColor = new Color(device, - SwtDrawingStyle.HOVER_SELECTION.getStrokeColor()); - } - if (SwtDrawingStyle.HOVER_SELECTION.getFillColor() != null) { - mHoverSelectFillColor = new Color(device, - SwtDrawingStyle.HOVER_SELECTION.getFillColor()); - } - } - - @Override - public void dispose() { - if (mHoverStrokeColor != null) { - mHoverStrokeColor.dispose(); - mHoverStrokeColor = null; - } - - if (mHoverFillColor != null) { - mHoverFillColor.dispose(); - mHoverFillColor = null; - } - - if (mHoverSelectStrokeColor != null) { - mHoverSelectStrokeColor.dispose(); - mHoverSelectStrokeColor = null; - } - - if (mHoverSelectFillColor != null) { - mHoverSelectFillColor.dispose(); - mHoverSelectFillColor = null; - } - } - - /** - * Sets the hover rectangle. The coordinates of the rectangle are in layout - * coordinates. The recipient is will own this rectangle. - * <p/> - * TODO: Consider switching input arguments to two {@link LayoutPoint}s so - * we don't have ambiguity about the coordinate system of these input - * parameters. - * <p/> - * - * @param x The top left x coordinate, in layout coordinates, of the hover. - * @param y The top left y coordinate, in layout coordinates, of the hover. - * @param w The width of the hover (in layout coordinates). - * @param h The height of the hover (in layout coordinates). - */ - public void setHover(int x, int y, int w, int h) { - mHoverRect = new Rectangle(x, y, w, h); - } - - /** - * Removes the hover for the next paint. - */ - public void clearHover() { - mHoverRect = null; - } - - @Override - public void paint(GC gc) { - if (mHoverRect != null) { - // Translate the hover rectangle (in canvas coordinates) to control - // coordinates - int x = mHScale.translate(mHoverRect.x); - int y = mVScale.translate(mHoverRect.y); - int w = mHScale.scale(mHoverRect.width); - int h = mVScale.scale(mHoverRect.height); - - - boolean hoverIsSelected = false; - List<SelectionItem> selections = mCanvas.getSelectionManager().getSelections(); - for (SelectionItem item : selections) { - if (mHoverRect.equals(item.getViewInfo().getSelectionRect())) { - hoverIsSelected = true; - break; - } - } - - Color stroke = hoverIsSelected ? mHoverSelectStrokeColor : mHoverStrokeColor; - Color fill = hoverIsSelected ? mHoverSelectFillColor : mHoverFillColor; - - if (stroke != null) { - int oldAlpha = gc.getAlpha(); - gc.setForeground(stroke); - gc.setLineStyle(hoverIsSelected ? - HOVER_SELECTION.getLineStyle() : HOVER.getLineStyle()); - gc.setAlpha(hoverIsSelected ? - HOVER_SELECTION.getStrokeAlpha() : HOVER.getStrokeAlpha()); - gc.drawRectangle(x, y, w, h); - gc.setAlpha(oldAlpha); - } - - if (fill != null) { - int oldAlpha = gc.getAlpha(); - gc.setAlpha(hoverIsSelected ? - HOVER_SELECTION.getFillAlpha() : HOVER.getFillAlpha()); - gc.setBackground(fill); - gc.fillRectangle(x, y, w, h); - gc.setAlpha(oldAlpha); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java deleted file mode 100644 index 4447eebd2..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * 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.annotations.NonNull; -import com.android.annotations.Nullable; - -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 mDisposeImage = true; - private boolean mMouseIn; - private Color mHoverColor; - private float mScale = 1.0f; - - /** - * Creates an ImageControl rendering the given image, which will be disposed when this - * control is disposed (unless the {@link #setDisposeImage} method is called to turn - * off auto dispose). - * - * @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 (unless the {@link #setDisposeImage} method is - * called to turn off auto dispose) - */ - public ImageControl(@NonNull Composite parent, int style, @Nullable Image image) { - super(parent, style | SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED); - mImage = image; - - addPaintListener(new PaintListener() { - @Override - public void paintControl(PaintEvent event) { - onPaint(event); - } - }); - } - - @Nullable - public Image getImage() { - return mImage; - } - - public void setImage(@Nullable Image image) { - if (mDisposeImage && mImage != null) { - mImage.dispose(); - } - mImage = image; - redraw(); - } - - public void fitToWidth(int width) { - if (mImage == null) { - return; - } - Rectangle imageRect = mImage.getBounds(); - int imageWidth = imageRect.width; - if (imageWidth <= width) { - mScale = 1.0f; - return; - } - - mScale = width / (float) imageWidth; - redraw(); - } - - public void setScale(float scale) { - mScale = scale; - } - - public float getScale() { - return mScale; - } - - public void setHoverColor(@Nullable Color hoverColor) { - if (mHoverColor != null) { - removeMouseTrackListener(this); - } - mHoverColor = hoverColor; - if (hoverColor != null) { - addMouseTrackListener(this); - } - } - - @Nullable - public Color getHoverColor() { - return mHoverColor; - } - - @Override - public void dispose() { - super.dispose(); - - if (mDisposeImage && mImage != null && !mImage.isDisposed()) { - mImage.dispose(); - } - mImage = null; - } - - public void setDisposeImage(boolean disposeImage) { - mDisposeImage = disposeImage; - } - - public boolean getDisposeImage() { - return mDisposeImage; - } - - @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; - - int oldGcAlias = gc.getAntialias(); - int oldGcInterpolation = gc.getInterpolation(); - if (mScale != 1.0f) { - destWidth = (int) (mScale * destWidth); - destHeight = (int) (mScale * destHeight); - gc.setAntialias(SWT.ON); - gc.setInterpolation(SWT.HIGH); - } - - gc.drawImage(mImage, 0, 0, imageWidth, imageHeight, rect.x + mLeftMargin, rect.y - + mTopMargin, destWidth, destHeight); - - gc.setAntialias(oldGcAlias); - gc.setInterpolation(oldGcInterpolation); - - 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 ---- - - @Override - public void mouseEnter(MouseEvent e) { - mMouseIn = true; - if (mHoverColor != null) { - redraw(); - } - } - - @Override - public void mouseExit(MouseEvent e) { - mMouseIn = false; - if (mHoverColor != null) { - redraw(); - } - } - - @Override - public void mouseHover(MouseEvent e) { - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java deleted file mode 100644 index a1363ecb1..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE; - -import com.android.SdkConstants; -import com.android.annotations.Nullable; -import com.android.ide.common.api.Rect; -import com.android.ide.common.rendering.api.IImageFactory; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.SWTException; -import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.PaletteData; - -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; -import java.awt.image.WritableRaster; -import java.lang.ref.SoftReference; - -/** - * The {@link ImageOverlay} class renders an image as an overlay. - */ -public class ImageOverlay extends Overlay implements IImageFactory { - /** - * Whether the image should be pre-scaled (scaled to the zoom level) once - * instead of dynamically during each paint; this is necessary on some - * platforms (see issue #19447) - */ - private static final boolean PRESCALE = - // Currently this is necessary on Linux because the "Cairo" library - // seems to be a bottleneck - SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX - && !(Boolean.getBoolean("adt.noprescale")); //$NON-NLS-1$ - - /** Current background image. Null when there's no image. */ - private Image mImage; - - /** A pre-scaled version of the image */ - private Image mPreScaledImage; - - /** Whether the rendered image should have a drop shadow */ - private boolean mShowDropShadow; - - /** Current background AWT image. This is created by {@link #getImage()}, which is called - * by the LayoutLib. */ - private SoftReference<BufferedImage> mAwtImage = new SoftReference<BufferedImage>(null); - - /** - * Strong reference to the image in the above soft reference, to prevent - * garbage collection when {@link PRESCALE} is set, until the scaled image - * is created (lazily as part of the next paint call, where this strong - * reference is nulled out and the above soft reference becomes eligible to - * be reclaimed when memory is low.) - */ - @SuppressWarnings("unused") // Used by the garbage collector to keep mAwtImage non-soft - private BufferedImage mAwtImageStrongRef; - - /** The associated {@link LayoutCanvas}. */ - private LayoutCanvas mCanvas; - - /** Vertical scaling & scrollbar information. */ - private CanvasTransform mVScale; - - /** Horizontal scaling & scrollbar information. */ - private CanvasTransform mHScale; - - /** - * Constructs an {@link ImageOverlay} tied to the given canvas. - * - * @param canvas The {@link LayoutCanvas} to paint the overlay over. - * @param hScale The horizontal scale information. - * @param vScale The vertical scale information. - */ - public ImageOverlay(LayoutCanvas canvas, CanvasTransform hScale, CanvasTransform vScale) { - mCanvas = canvas; - mHScale = hScale; - mVScale = vScale; - } - - @Override - public void create(Device device) { - super.create(device); - } - - @Override - public void dispose() { - if (mImage != null) { - mImage.dispose(); - mImage = null; - } - if (mPreScaledImage != null) { - mPreScaledImage.dispose(); - mPreScaledImage = null; - } - } - - /** - * Sets the image to be drawn as an overlay from the passed in AWT - * {@link BufferedImage} (which will be converted to an SWT image). - * <p/> - * The image <b>can</b> be null, which is the case when we are dealing with - * an empty document. - * - * @param awtImage The AWT image to be rendered as an SWT image. - * @param isAlphaChannelImage whether the alpha channel of the image is relevant - * @return The corresponding SWT image, or null. - */ - public synchronized Image setImage(BufferedImage awtImage, boolean isAlphaChannelImage) { - mShowDropShadow = !isAlphaChannelImage; - - BufferedImage oldAwtImage = mAwtImage.get(); - if (awtImage != oldAwtImage || awtImage == null) { - mAwtImage.clear(); - mAwtImageStrongRef = null; - - if (mImage != null) { - mImage.dispose(); - } - - if (awtImage == null) { - mImage = null; - } else { - mImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), awtImage, - isAlphaChannelImage, -1); - } - } else { - assert awtImage instanceof SwtReadyBufferedImage; - - if (isAlphaChannelImage) { - if (mImage != null) { - mImage.dispose(); - } - - mImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), awtImage, true, -1); - } else { - Image prev = mImage; - mImage = ((SwtReadyBufferedImage)awtImage).getSwtImage(); - if (prev != mImage && prev != null) { - prev.dispose(); - } - } - } - - if (mPreScaledImage != null) { - // Force refresh on next paint - mPreScaledImage.dispose(); - mPreScaledImage = null; - } - - return mImage; - } - - /** - * Returns the currently painted image, or null if none has been set - * - * @return the currently painted image or null - */ - public Image getImage() { - return mImage; - } - - /** - * Returns the currently rendered image, or null if none has been set - * - * @return the currently rendered image or null - */ - @Nullable - BufferedImage getAwtImage() { - BufferedImage awtImage = mAwtImage.get(); - if (awtImage == null && mImage != null) { - awtImage = SwtUtils.convertToAwt(mImage); - } - - return awtImage; - } - - /** - * Returns whether this image overlay should be painted with a drop shadow. - * This is usually the case, but not for transparent themes like the dialog - * theme (Theme.*Dialog), which already provides its own shadow. - * - * @return true if the image overlay should be shown with a drop shadow. - */ - public boolean getShowDropShadow() { - return mShowDropShadow; - } - - @Override - public synchronized void paint(GC gc) { - if (mImage != null) { - boolean valid = mCanvas.getViewHierarchy().isValid(); - mCanvas.ensureZoomed(); - if (!valid) { - gc_setAlpha(gc, 128); // half-transparent - } - - CanvasTransform hi = mHScale; - CanvasTransform vi = mVScale; - - // On some platforms, dynamic image scaling is very slow (see issue #19447) so - // compute a pre-scaled version of the image once and render that instead. - // This is done lazily in paint rather than when the image changes because - // the image must be rescaled each time the zoom level changes, which varies - // independently from when the image changes. - BufferedImage awtImage = mAwtImage.get(); - if (PRESCALE && awtImage != null) { - int imageWidth = (mPreScaledImage == null) ? 0 - : mPreScaledImage.getImageData().width - - (mShowDropShadow ? SHADOW_SIZE : 0); - if (mPreScaledImage == null || imageWidth != hi.getScaledImgSize()) { - double xScale = hi.getScaledImgSize() / (double) awtImage.getWidth(); - double yScale = vi.getScaledImgSize() / (double) awtImage.getHeight(); - BufferedImage scaledAwtImage; - - // NOTE: == comparison on floating point numbers is okay - // here because we normalize the scaling factor - // to an exact 1.0 in the zooming code when the value gets - // near 1.0 to make painting more efficient in the presence - // of rounding errors. - if (xScale == 1.0 && yScale == 1.0) { - // Scaling to 100% is easy! - scaledAwtImage = awtImage; - - if (mShowDropShadow) { - // Just need to draw drop shadows - scaledAwtImage = ImageUtils.createRectangularDropShadow(awtImage); - } - } else { - if (mShowDropShadow) { - scaledAwtImage = ImageUtils.scale(awtImage, xScale, yScale, - SHADOW_SIZE, SHADOW_SIZE); - ImageUtils.drawRectangleShadow(scaledAwtImage, 0, 0, - scaledAwtImage.getWidth() - SHADOW_SIZE, - scaledAwtImage.getHeight() - SHADOW_SIZE); - } else { - scaledAwtImage = ImageUtils.scale(awtImage, xScale, yScale); - } - } - - if (mPreScaledImage != null && !mPreScaledImage.isDisposed()) { - mPreScaledImage.dispose(); - } - mPreScaledImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), scaledAwtImage, - true /*transferAlpha*/, -1); - // We can't just clear the mAwtImageStrongRef here, because if the - // zooming factor changes, we may need to use it again - } - - if (mPreScaledImage != null) { - gc.drawImage(mPreScaledImage, hi.translate(0), vi.translate(0)); - } - return; - } - - // we only anti-alias when reducing the image size. - int oldAlias = -2; - if (hi.getScale() < 1.0) { - oldAlias = gc_setAntialias(gc, SWT.ON); - } - - int srcX = 0; - int srcY = 0; - int srcWidth = hi.getImgSize(); - int srcHeight = vi.getImgSize(); - int destX = hi.translate(0); - int destY = vi.translate(0); - int destWidth = hi.getScaledImgSize(); - int destHeight = vi.getScaledImgSize(); - - gc.drawImage(mImage, - srcX, srcY, srcWidth, srcHeight, - destX, destY, destWidth, destHeight); - - if (mShowDropShadow) { - SwtUtils.drawRectangleShadow(gc, destX, destY, destWidth, destHeight); - } - - if (oldAlias != -2) { - gc_setAntialias(gc, oldAlias); - } - - if (!valid) { - gc_setAlpha(gc, 255); // opaque - } - } - } - - /** - * Sets the alpha for the given GC. - * <p/> - * Alpha may not work on all platforms and may fail with an exception, which - * is hidden here (false is returned in that case). - * - * @param gc the GC to change - * @param alpha the new alpha, 0 for transparent, 255 for opaque. - * @return True if the operation worked, false if it failed with an - * exception. - * @see GC#setAlpha(int) - */ - private boolean gc_setAlpha(GC gc, int alpha) { - try { - gc.setAlpha(alpha); - return true; - } catch (SWTException e) { - return false; - } - } - - /** - * Sets the non-text antialias flag for the given GC. - * <p/> - * Antialias may not work on all platforms and may fail with an exception, - * which is hidden here (-2 is returned in that case). - * - * @param gc the GC to change - * @param alias One of {@link SWT#DEFAULT}, {@link SWT#ON}, {@link SWT#OFF}. - * @return The previous aliasing mode if the operation worked, or -2 if it - * failed with an exception. - * @see GC#setAntialias(int) - */ - private int gc_setAntialias(GC gc, int alias) { - try { - int old = gc.getAntialias(); - gc.setAntialias(alias); - return old; - } catch (SWTException e) { - return -2; - } - } - - /** - * Custom {@link BufferedImage} class able to convert itself into an SWT {@link Image} - * efficiently. - * - * The BufferedImage also contains an instance of {@link ImageData} that's kept around - * and used to create new SWT {@link Image} objects in {@link #getSwtImage()}. - * - */ - private static final class SwtReadyBufferedImage extends BufferedImage { - - private final ImageData mImageData; - private final Device mDevice; - - /** - * Creates the image with a given model, raster and SWT {@link ImageData} - * @param model the color model - * @param raster the image raster - * @param imageData the SWT image data. - * @param device the {@link Device} in which the SWT image will be painted. - */ - private SwtReadyBufferedImage(int width, int height, ImageData imageData, Device device) { - super(width, height, BufferedImage.TYPE_INT_ARGB); - mImageData = imageData; - mDevice = device; - } - - /** - * Returns a new {@link Image} object initialized with the content of the BufferedImage. - * @return the image object. - */ - private Image getSwtImage() { - // transfer the content of the bufferedImage into the image data. - WritableRaster raster = getRaster(); - int[] imageDataBuffer = ((DataBufferInt) raster.getDataBuffer()).getData(); - - mImageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0); - - return new Image(mDevice, mImageData); - } - - /** - * Creates a new {@link SwtReadyBufferedImage}. - * @param w the width of the image - * @param h the height of the image - * @param device the device in which the SWT image will be painted - * @return a new {@link SwtReadyBufferedImage} object - */ - private static SwtReadyBufferedImage createImage(int w, int h, Device device) { - // NOTE: We can't make this image bigger to accommodate the drop shadow directly - // (such that we could paint one into the image after a layoutlib render) - // since this image is in the full resolution of the device, and gets scaled - // to fit in the layout editor. This would have the net effect of causing - // the drop shadow to get zoomed/scaled along with the scene, making a tiny - // drop shadow for tablet layouts, a huge drop shadow for tiny QVGA screens, etc. - - ImageData imageData = new ImageData(w, h, 32, - new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF)); - - SwtReadyBufferedImage swtReadyImage = new SwtReadyBufferedImage(w, h, - imageData, device); - - return swtReadyImage; - } - } - - /** - * Implementation of {@link IImageFactory#getImage(int, int)}. - */ - @Override - public BufferedImage getImage(int w, int h) { - BufferedImage awtImage = mAwtImage.get(); - if (awtImage == null || - awtImage.getWidth() != w || - awtImage.getHeight() != h) { - mAwtImage.clear(); - awtImage = SwtReadyBufferedImage.createImage(w, h, getDevice()); - mAwtImage = new SoftReference<BufferedImage>(awtImage); - if (PRESCALE) { - mAwtImageStrongRef = awtImage; - } - } - - return awtImage; - } - - /** - * Returns the bounds of the current image, or null - * - * @return the bounds of the current image, or null - */ - public Rect getImageBounds() { - if (mImage == null) { - return null; - } - - return new Rect(0, 0, mImage.getImageData().width, mImage.getImageData().height); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java deleted file mode 100644 index b5bc9aa72..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java +++ /dev/null @@ -1,979 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.DOT_9PNG; -import static com.android.SdkConstants.DOT_BMP; -import static com.android.SdkConstants.DOT_GIF; -import static com.android.SdkConstants.DOT_JPG; -import static com.android.SdkConstants.DOT_PNG; -import static com.android.utils.SdkUtils.endsWithIgnoreCase; -import static java.awt.RenderingHints.KEY_ANTIALIASING; -import static java.awt.RenderingHints.KEY_INTERPOLATION; -import static java.awt.RenderingHints.KEY_RENDERING; -import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON; -import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR; -import static java.awt.RenderingHints.VALUE_RENDER_QUALITY; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.Rect; -import com.android.ide.eclipse.adt.AdtPlugin; - -import org.eclipse.swt.graphics.RGB; -import org.eclipse.swt.graphics.Rectangle; - -import java.awt.AlphaComposite; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; -import java.io.IOException; -import java.io.InputStream; -import java.util.Iterator; -import java.util.List; - -import javax.imageio.ImageIO; - -/** - * Utilities related to image processing. - */ -public class ImageUtils { - /** - * Returns true if the given image has no dark pixels - * - * @param image the image to be checked for dark pixels - * @return true if no dark pixels were found - */ - public static boolean containsDarkPixels(BufferedImage image) { - for (int y = 0, height = image.getHeight(); y < height; y++) { - for (int x = 0, width = image.getWidth(); x < width; x++) { - int pixel = image.getRGB(x, y); - if ((pixel & 0xFF000000) != 0) { - int r = (pixel & 0xFF0000) >> 16; - int g = (pixel & 0x00FF00) >> 8; - int b = (pixel & 0x0000FF); - - // One perceived luminance formula is (0.299*red + 0.587*green + 0.114*blue) - // In order to keep this fast since we don't need a very accurate - // measure, I'll just estimate this with integer math: - long brightness = (299L*r + 587*g + 114*b) / 1000; - if (brightness < 128) { - return true; - } - } - } - } - return false; - } - - /** - * Returns the perceived brightness of the given RGB integer on a scale from 0 to 255 - * - * @param rgb the RGB triplet, 8 bits each - * @return the perceived brightness, with 0 maximally dark and 255 maximally bright - */ - public static int getBrightness(int rgb) { - if ((rgb & 0xFFFFFF) != 0) { - int r = (rgb & 0xFF0000) >> 16; - int g = (rgb & 0x00FF00) >> 8; - int b = (rgb & 0x0000FF); - // See the containsDarkPixels implementation for details - return (int) ((299L*r + 587*g + 114*b) / 1000); - } - - return 0; - } - - /** - * Converts an alpha-red-green-blue integer color into an {@link RGB} color. - * <p> - * <b>NOTE</b> - this will drop the alpha value since {@link RGB} objects do not - * contain transparency information. - * - * @param rgb the RGB integer to convert to a color description - * @return the color description corresponding to the integer - */ - public static RGB intToRgb(int rgb) { - return new RGB((rgb & 0xFF0000) >>> 16, (rgb & 0xFF00) >>> 8, rgb & 0xFF); - } - - /** - * Converts an {@link RGB} color into a alpha-red-green-blue integer - * - * @param rgb the RGB color descriptor to convert - * @param alpha the amount of alpha to add into the color integer (since the - * {@link RGB} objects do not contain an alpha channel) - * @return an integer corresponding to the {@link RGB} color - */ - public static int rgbToInt(RGB rgb, int alpha) { - return alpha << 24 | (rgb.red << 16) | (rgb.green << 8) | rgb.blue; - } - - /** - * Crops blank pixels from the edges of the image and returns the cropped result. We - * crop off pixels that are blank (meaning they have an alpha value = 0). Note that - * this is not the same as pixels that aren't opaque (an alpha value other than 255). - * - * @param image the image to be cropped - * @param initialCrop If not null, specifies a rectangle which contains an initial - * crop to continue. This can be used to crop an image where you already - * know about margins in the image - * @return a cropped version of the source image, or null if the whole image was blank - * and cropping completely removed everything - */ - @Nullable - public static BufferedImage cropBlank( - @NonNull BufferedImage image, - @Nullable Rect initialCrop) { - return cropBlank(image, initialCrop, image.getType()); - } - - /** - * Crops blank pixels from the edges of the image and returns the cropped result. We - * crop off pixels that are blank (meaning they have an alpha value = 0). Note that - * this is not the same as pixels that aren't opaque (an alpha value other than 255). - * - * @param image the image to be cropped - * @param initialCrop If not null, specifies a rectangle which contains an initial - * crop to continue. This can be used to crop an image where you already - * know about margins in the image - * @param imageType the type of {@link BufferedImage} to create - * @return a cropped version of the source image, or null if the whole image was blank - * and cropping completely removed everything - */ - public static BufferedImage cropBlank(BufferedImage image, Rect initialCrop, int imageType) { - CropFilter filter = new CropFilter() { - @Override - public boolean crop(BufferedImage bufferedImage, int x, int y) { - int rgb = bufferedImage.getRGB(x, y); - return (rgb & 0xFF000000) == 0x00000000; - // TODO: Do a threshold of 80 instead of just 0? Might give better - // visual results -- e.g. check <= 0x80000000 - } - }; - return crop(image, filter, initialCrop, imageType); - } - - /** - * Crops pixels of a given color from the edges of the image and returns the cropped - * result. - * - * @param image the image to be cropped - * @param blankArgb the color considered to be blank, as a 32 pixel integer with 8 - * bits of alpha, red, green and blue - * @param initialCrop If not null, specifies a rectangle which contains an initial - * crop to continue. This can be used to crop an image where you already - * know about margins in the image - * @return a cropped version of the source image, or null if the whole image was blank - * and cropping completely removed everything - */ - @Nullable - public static BufferedImage cropColor( - @NonNull BufferedImage image, - final int blankArgb, - @Nullable Rect initialCrop) { - return cropColor(image, blankArgb, initialCrop, image.getType()); - } - - /** - * Crops pixels of a given color from the edges of the image and returns the cropped - * result. - * - * @param image the image to be cropped - * @param blankArgb the color considered to be blank, as a 32 pixel integer with 8 - * bits of alpha, red, green and blue - * @param initialCrop If not null, specifies a rectangle which contains an initial - * crop to continue. This can be used to crop an image where you already - * know about margins in the image - * @param imageType the type of {@link BufferedImage} to create - * @return a cropped version of the source image, or null if the whole image was blank - * and cropping completely removed everything - */ - public static BufferedImage cropColor(BufferedImage image, - final int blankArgb, Rect initialCrop, int imageType) { - CropFilter filter = new CropFilter() { - @Override - public boolean crop(BufferedImage bufferedImage, int x, int y) { - return blankArgb == bufferedImage.getRGB(x, y); - } - }; - return crop(image, filter, initialCrop, imageType); - } - - /** - * Interface implemented by cropping functions that determine whether - * a pixel should be cropped or not. - */ - private static interface CropFilter { - /** - * Returns true if the pixel is should be cropped. - * - * @param image the image containing the pixel in question - * @param x the x position of the pixel - * @param y the y position of the pixel - * @return true if the pixel should be cropped (for example, is blank) - */ - boolean crop(BufferedImage image, int x, int y); - } - - private static BufferedImage crop(BufferedImage image, CropFilter filter, Rect initialCrop, - int imageType) { - if (image == null) { - return null; - } - - // First, determine the dimensions of the real image within the image - int x1, y1, x2, y2; - if (initialCrop != null) { - x1 = initialCrop.x; - y1 = initialCrop.y; - x2 = initialCrop.x + initialCrop.w; - y2 = initialCrop.y + initialCrop.h; - } else { - x1 = 0; - y1 = 0; - x2 = image.getWidth(); - y2 = image.getHeight(); - } - - // Nothing left to crop - if (x1 == x2 || y1 == y2) { - return null; - } - - // This algorithm is a bit dumb -- it just scans along the edges looking for - // a pixel that shouldn't be cropped. I could maybe try to make it smarter by - // for example doing a binary search to quickly eliminate large empty areas to - // the right and bottom -- but this is slightly tricky with components like the - // AnalogClock where I could accidentally end up finding a blank horizontal or - // vertical line somewhere in the middle of the rendering of the clock, so for now - // we do the dumb thing -- not a big deal since we tend to crop reasonably - // small images. - - // First determine top edge - topEdge: for (; y1 < y2; y1++) { - for (int x = x1; x < x2; x++) { - if (!filter.crop(image, x, y1)) { - break topEdge; - } - } - } - - if (y1 == image.getHeight()) { - // The image is blank - return null; - } - - // Next determine left edge - leftEdge: for (; x1 < x2; x1++) { - for (int y = y1; y < y2; y++) { - if (!filter.crop(image, x1, y)) { - break leftEdge; - } - } - } - - // Next determine right edge - rightEdge: for (; x2 > x1; x2--) { - for (int y = y1; y < y2; y++) { - if (!filter.crop(image, x2 - 1, y)) { - break rightEdge; - } - } - } - - // Finally determine bottom edge - bottomEdge: for (; y2 > y1; y2--) { - for (int x = x1; x < x2; x++) { - if (!filter.crop(image, x, y2 - 1)) { - break bottomEdge; - } - } - } - - // No need to crop? - if (x1 == 0 && y1 == 0 && x2 == image.getWidth() && y2 == image.getHeight()) { - return image; - } - - if (x1 == x2 || y1 == y2) { - // Nothing left after crop -- blank image - return null; - } - - int width = x2 - x1; - int height = y2 - y1; - - // Now extract the sub-image - if (imageType == -1) { - imageType = image.getType(); - } - if (imageType == BufferedImage.TYPE_CUSTOM) { - imageType = BufferedImage.TYPE_INT_ARGB; - } - BufferedImage cropped = new BufferedImage(width, height, imageType); - Graphics g = cropped.getGraphics(); - g.drawImage(image, 0, 0, width, height, x1, y1, x2, y2, null); - - g.dispose(); - - return cropped; - } - - /** - * Creates a drop shadow of a given image and returns a new image which shows the - * input image on top of its drop shadow. - * <p> - * <b>NOTE: If the shape is rectangular and opaque, consider using - * {@link #drawRectangleShadow(Graphics, int, int, int, int)} instead.</b> - * - * @param source the source image to be shadowed - * @param shadowSize the size of the shadow in pixels - * @param shadowOpacity the opacity of the shadow, with 0=transparent and 1=opaque - * @param shadowRgb the RGB int to use for the shadow color - * @return a new image with the source image on top of its shadow - */ - public static BufferedImage createDropShadow(BufferedImage source, int shadowSize, - float shadowOpacity, int shadowRgb) { - - // This code is based on - // http://www.jroller.com/gfx/entry/non_rectangular_shadow - - BufferedImage image = new BufferedImage(source.getWidth() + shadowSize * 2, - source.getHeight() + shadowSize * 2, - BufferedImage.TYPE_INT_ARGB); - - Graphics2D g2 = image.createGraphics(); - g2.drawImage(source, null, shadowSize, shadowSize); - - int dstWidth = image.getWidth(); - int dstHeight = image.getHeight(); - - int left = (shadowSize - 1) >> 1; - int right = shadowSize - left; - int xStart = left; - int xStop = dstWidth - right; - int yStart = left; - int yStop = dstHeight - right; - - shadowRgb = shadowRgb & 0x00FFFFFF; - - int[] aHistory = new int[shadowSize]; - int historyIdx = 0; - - int aSum; - - int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - int lastPixelOffset = right * dstWidth; - float sumDivider = shadowOpacity / shadowSize; - - // horizontal pass - for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) { - aSum = 0; - historyIdx = 0; - for (int x = 0; x < shadowSize; x++, bufferOffset++) { - int a = dataBuffer[bufferOffset] >>> 24; - aHistory[x] = a; - aSum += a; - } - - bufferOffset -= right; - - for (int x = xStart; x < xStop; x++, bufferOffset++) { - int a = (int) (aSum * sumDivider); - dataBuffer[bufferOffset] = a << 24 | shadowRgb; - - // subtract the oldest pixel from the sum - aSum -= aHistory[historyIdx]; - - // get the latest pixel - a = dataBuffer[bufferOffset + right] >>> 24; - aHistory[historyIdx] = a; - aSum += a; - - if (++historyIdx >= shadowSize) { - historyIdx -= shadowSize; - } - } - } - // vertical pass - for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) { - aSum = 0; - historyIdx = 0; - for (int y = 0; y < shadowSize; y++, bufferOffset += dstWidth) { - int a = dataBuffer[bufferOffset] >>> 24; - aHistory[y] = a; - aSum += a; - } - - bufferOffset -= lastPixelOffset; - - for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) { - int a = (int) (aSum * sumDivider); - dataBuffer[bufferOffset] = a << 24 | shadowRgb; - - // subtract the oldest pixel from the sum - aSum -= aHistory[historyIdx]; - - // get the latest pixel - a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24; - aHistory[historyIdx] = a; - aSum += a; - - if (++historyIdx >= shadowSize) { - historyIdx -= shadowSize; - } - } - } - - g2.drawImage(source, null, 0, 0); - g2.dispose(); - - return image; - } - - /** - * Draws a rectangular drop shadow (of size {@link #SHADOW_SIZE} by - * {@link #SHADOW_SIZE} around the given source and returns a new image with - * both combined - * - * @param source the source image - * @return the source image with a drop shadow on the bottom and right - */ - public static BufferedImage createRectangularDropShadow(BufferedImage source) { - int type = source.getType(); - if (type == BufferedImage.TYPE_CUSTOM) { - type = BufferedImage.TYPE_INT_ARGB; - } - - int width = source.getWidth(); - int height = source.getHeight(); - BufferedImage image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE, type); - Graphics g = image.getGraphics(); - g.drawImage(source, 0, 0, width, height, null); - ImageUtils.drawRectangleShadow(image, 0, 0, width, height); - g.dispose(); - - return image; - } - - /** - * Draws a drop shadow for the given rectangle into the given context. It - * will not draw anything if the rectangle is smaller than a minimum - * determined by the assets used to draw the shadow graphics. - * The size of the shadow is {@link #SHADOW_SIZE}. - * - * @param image the image to draw the shadow into - * @param x the left coordinate of the left hand side of the rectangle - * @param y the top coordinate of the top of the rectangle - * @param width the width of the rectangle - * @param height the height of the rectangle - */ - public static final void drawRectangleShadow(BufferedImage image, - int x, int y, int width, int height) { - Graphics gc = image.getGraphics(); - try { - drawRectangleShadow(gc, x, y, width, height); - } finally { - gc.dispose(); - } - } - - /** - * Draws a small drop shadow for the given rectangle into the given context. It - * will not draw anything if the rectangle is smaller than a minimum - * determined by the assets used to draw the shadow graphics. - * The size of the shadow is {@link #SMALL_SHADOW_SIZE}. - * - * @param image the image to draw the shadow into - * @param x the left coordinate of the left hand side of the rectangle - * @param y the top coordinate of the top of the rectangle - * @param width the width of the rectangle - * @param height the height of the rectangle - */ - public static final void drawSmallRectangleShadow(BufferedImage image, - int x, int y, int width, int height) { - Graphics gc = image.getGraphics(); - try { - drawSmallRectangleShadow(gc, x, y, width, height); - } finally { - gc.dispose(); - } - } - - /** - * The width and height of the drop shadow painted by - * {@link #drawRectangleShadow(Graphics, int, int, int, int)} - */ - public static final int SHADOW_SIZE = 20; // DO NOT EDIT. This corresponds to bitmap graphics - - /** - * The width and height of the drop shadow painted by - * {@link #drawSmallRectangleShadow(Graphics, int, int, int, int)} - */ - public static final int SMALL_SHADOW_SIZE = 10; // DO NOT EDIT. Corresponds to bitmap graphics - - /** - * Draws a drop shadow for the given rectangle into the given context. It - * will not draw anything if the rectangle is smaller than a minimum - * determined by the assets used to draw the shadow graphics. - * <p> - * This corresponds to - * {@link SwtUtils#drawRectangleShadow(org.eclipse.swt.graphics.GC, int, int, int, int)}, - * but applied to an AWT graphics object instead, such that no image - * conversion has to be performed. - * <p> - * Make sure to keep changes in the visual appearance here in sync with the - * AWT version in - * {@link SwtUtils#drawRectangleShadow(org.eclipse.swt.graphics.GC, int, int, int, int)}. - * - * @param gc the graphics context to draw into - * @param x the left coordinate of the left hand side of the rectangle - * @param y the top coordinate of the top of the rectangle - * @param width the width of the rectangle - * @param height the height of the rectangle - */ - public static final void drawRectangleShadow(Graphics gc, - int x, int y, int width, int height) { - if (sShadowBottomLeft == null) { - // Shadow graphics. This was generated by creating a drop shadow in - // Gimp, using the parameters x offset=10, y offset=10, blur radius=10, - // color=black, and opacity=51. These values attempt to make a shadow - // that is legible both for dark and light themes, on top of the - // canvas background (rgb(150,150,150). Darker shadows would tend to - // blend into the foreground for a dark holo screen, and lighter shadows - // would be hard to spot on the canvas background. If you make adjustments, - // make sure to check the shadow with both dark and light themes. - // - // After making the graphics, I cut out the top right, bottom left - // and bottom right corners as 20x20 images, and these are reproduced by - // painting them in the corresponding places in the target graphics context. - // I then grabbed a single horizontal gradient line from the middle of the - // right edge,and a single vertical gradient line from the bottom. These - // are then painted scaled/stretched in the target to fill the gaps between - // the three corner images. - // - // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right - sShadowBottomLeft = readImage("shadow-bl.png"); //$NON-NLS-1$ - sShadowBottom = readImage("shadow-b.png"); //$NON-NLS-1$ - sShadowBottomRight = readImage("shadow-br.png"); //$NON-NLS-1$ - sShadowRight = readImage("shadow-r.png"); //$NON-NLS-1$ - sShadowTopRight = readImage("shadow-tr.png"); //$NON-NLS-1$ - assert sShadowBottomLeft != null; - assert sShadowBottomRight.getWidth() == SHADOW_SIZE; - assert sShadowBottomRight.getHeight() == SHADOW_SIZE; - } - - int blWidth = sShadowBottomLeft.getWidth(); - int trHeight = sShadowTopRight.getHeight(); - if (width < blWidth) { - return; - } - if (height < trHeight) { - return; - } - - gc.drawImage(sShadowBottomLeft, x, y + height, null); - gc.drawImage(sShadowBottomRight, x + width, y + height, null); - gc.drawImage(sShadowTopRight, x + width, y, null); - gc.drawImage(sShadowBottom, - x + sShadowBottomLeft.getWidth(), y + height, - x + width, y + height + sShadowBottom.getHeight(), - 0, 0, sShadowBottom.getWidth(), sShadowBottom.getHeight(), - null); - gc.drawImage(sShadowRight, - x + width, y + sShadowTopRight.getHeight(), - x + width + sShadowRight.getWidth(), y + height, - 0, 0, sShadowRight.getWidth(), sShadowRight.getHeight(), - null); - } - - /** - * Draws a small drop shadow for the given rectangle into the given context. It - * will not draw anything if the rectangle is smaller than a minimum - * determined by the assets used to draw the shadow graphics. - * <p> - * - * @param gc the graphics context to draw into - * @param x the left coordinate of the left hand side of the rectangle - * @param y the top coordinate of the top of the rectangle - * @param width the width of the rectangle - * @param height the height of the rectangle - */ - public static final void drawSmallRectangleShadow(Graphics gc, - int x, int y, int width, int height) { - if (sShadow2BottomLeft == null) { - // Shadow graphics. This was generated by creating a drop shadow in - // Gimp, using the parameters x offset=5, y offset=%, blur radius=5, - // color=black, and opacity=51. These values attempt to make a shadow - // that is legible both for dark and light themes, on top of the - // canvas background (rgb(150,150,150). Darker shadows would tend to - // blend into the foreground for a dark holo screen, and lighter shadows - // would be hard to spot on the canvas background. If you make adjustments, - // make sure to check the shadow with both dark and light themes. - // - // After making the graphics, I cut out the top right, bottom left - // and bottom right corners as 20x20 images, and these are reproduced by - // painting them in the corresponding places in the target graphics context. - // I then grabbed a single horizontal gradient line from the middle of the - // right edge,and a single vertical gradient line from the bottom. These - // are then painted scaled/stretched in the target to fill the gaps between - // the three corner images. - // - // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right - sShadow2BottomLeft = readImage("shadow2-bl.png"); //$NON-NLS-1$ - sShadow2Bottom = readImage("shadow2-b.png"); //$NON-NLS-1$ - sShadow2BottomRight = readImage("shadow2-br.png"); //$NON-NLS-1$ - sShadow2Right = readImage("shadow2-r.png"); //$NON-NLS-1$ - sShadow2TopRight = readImage("shadow2-tr.png"); //$NON-NLS-1$ - assert sShadow2BottomLeft != null; - assert sShadow2TopRight != null; - assert sShadow2BottomRight.getWidth() == SMALL_SHADOW_SIZE; - assert sShadow2BottomRight.getHeight() == SMALL_SHADOW_SIZE; - } - - int blWidth = sShadow2BottomLeft.getWidth(); - int trHeight = sShadow2TopRight.getHeight(); - if (width < blWidth) { - return; - } - if (height < trHeight) { - return; - } - - gc.drawImage(sShadow2BottomLeft, x, y + height, null); - gc.drawImage(sShadow2BottomRight, x + width, y + height, null); - gc.drawImage(sShadow2TopRight, x + width, y, null); - gc.drawImage(sShadow2Bottom, - x + sShadow2BottomLeft.getWidth(), y + height, - x + width, y + height + sShadow2Bottom.getHeight(), - 0, 0, sShadow2Bottom.getWidth(), sShadow2Bottom.getHeight(), - null); - gc.drawImage(sShadow2Right, - x + width, y + sShadow2TopRight.getHeight(), - x + width + sShadow2Right.getWidth(), y + height, - 0, 0, sShadow2Right.getWidth(), sShadow2Right.getHeight(), - null); - } - - /** - * Reads the given image from the plugin folder - * - * @param name the name of the image (including file extension) - * @return the corresponding image, or null if something goes wrong - */ - @Nullable - public static BufferedImage readImage(@NonNull String name) { - InputStream stream = ImageUtils.class.getResourceAsStream("/icons/" + name); //$NON-NLS-1$ - if (stream != null) { - try { - return ImageIO.read(stream); - } catch (IOException e) { - AdtPlugin.log(e, "Could not read %1$s", name); - } finally { - try { - stream.close(); - } catch (IOException e) { - // Dumb API - } - } - } - - return null; - } - - // Normal drop shadow - private static BufferedImage sShadowBottomLeft; - private static BufferedImage sShadowBottom; - private static BufferedImage sShadowBottomRight; - private static BufferedImage sShadowRight; - private static BufferedImage sShadowTopRight; - - // Small drop shadow - private static BufferedImage sShadow2BottomLeft; - private static BufferedImage sShadow2Bottom; - private static BufferedImage sShadow2BottomRight; - private static BufferedImage sShadow2Right; - private static BufferedImage sShadow2TopRight; - - /** - * Returns a bounding rectangle for the given list of rectangles. If the list is - * empty, the bounding rectangle is null. - * - * @param items the list of rectangles to compute a bounding rectangle for (may not be - * null) - * @return a bounding rectangle of the passed in rectangles, or null if the list is - * empty - */ - public static Rectangle getBoundingRectangle(List<Rectangle> items) { - Iterator<Rectangle> iterator = items.iterator(); - if (!iterator.hasNext()) { - return null; - } - - Rectangle bounds = iterator.next(); - Rectangle union = new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height); - while (iterator.hasNext()) { - union.add(iterator.next()); - } - - 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; - int imageType = source.getType(); - if (imageType == BufferedImage.TYPE_CUSTOM) { - imageType = BufferedImage.TYPE_INT_ARGB; - } - BufferedImage sub = new BufferedImage(width, height, imageType); - Graphics g = sub.getGraphics(); - g.drawImage(source, 0, 0, width, height, x1, y1, x2, y2, null); - g.dispose(); - - return sub; - } - - /** - * Returns the color value represented by the given string value - * @param value the color value - * @return the color as an int - * @throw NumberFormatException if the conversion failed. - */ - public static int getColor(String value) { - // Copied from ResourceHelper in layoutlib - if (value != null) { - if (value.startsWith("#") == false) { //$NON-NLS-1$ - throw new NumberFormatException( - String.format("Color value '%s' must start with #", value)); - } - - value = value.substring(1); - - // make sure it's not longer than 32bit - if (value.length() > 8) { - throw new NumberFormatException(String.format( - "Color value '%s' is too long. Format is either" + - "#AARRGGBB, #RRGGBB, #RGB, or #ARGB", - value)); - } - - if (value.length() == 3) { // RGB format - char[] color = new char[8]; - color[0] = color[1] = 'F'; - color[2] = color[3] = value.charAt(0); - color[4] = color[5] = value.charAt(1); - color[6] = color[7] = value.charAt(2); - value = new String(color); - } else if (value.length() == 4) { // ARGB format - char[] color = new char[8]; - color[0] = color[1] = value.charAt(0); - color[2] = color[3] = value.charAt(1); - color[4] = color[5] = value.charAt(2); - color[6] = color[7] = value.charAt(3); - value = new String(color); - } else if (value.length() == 6) { - value = "FF" + value; //$NON-NLS-1$ - } - - // this is a RRGGBB or AARRGGBB value - - // Integer.parseInt will fail to parse strings like "ff191919", so we use - // a Long, but cast the result back into an int, since we know that we're only - // dealing with 32 bit values. - return (int)Long.parseLong(value, 16); - } - - throw new NumberFormatException(); - } - - /** - * Resize the given image - * - * @param source the image to be scaled - * @param xScale x scale - * @param yScale y scale - * @return the scaled image - */ - public static BufferedImage scale(BufferedImage source, double xScale, double yScale) { - return scale(source, xScale, yScale, 0, 0); - } - - /** - * Resize the given image - * - * @param source the image to be scaled - * @param xScale x scale - * @param yScale y scale - * @param rightMargin extra margin to add on the right - * @param bottomMargin extra margin to add on the bottom - * @return the scaled image - */ - public static BufferedImage scale(BufferedImage source, double xScale, double yScale, - int rightMargin, int bottomMargin) { - int sourceWidth = source.getWidth(); - int sourceHeight = source.getHeight(); - int destWidth = Math.max(1, (int) (xScale * sourceWidth)); - int destHeight = Math.max(1, (int) (yScale * sourceHeight)); - int imageType = source.getType(); - if (imageType == BufferedImage.TYPE_CUSTOM) { - imageType = BufferedImage.TYPE_INT_ARGB; - } - if (xScale > 0.5 && yScale > 0.5) { - BufferedImage scaled = - new BufferedImage(destWidth + rightMargin, destHeight + bottomMargin, imageType); - Graphics2D g2 = scaled.createGraphics(); - g2.setComposite(AlphaComposite.Src); - g2.setColor(new Color(0, true)); - g2.fillRect(0, 0, destWidth + rightMargin, destHeight + bottomMargin); - g2.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR); - g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY); - g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); - g2.drawImage(source, 0, 0, destWidth, destHeight, 0, 0, sourceWidth, sourceHeight, - null); - g2.dispose(); - return scaled; - } else { - // When creating a thumbnail, using the above code doesn't work very well; - // you get some visible artifacts, especially for text. Instead use the - // technique of repeatedly scaling the image into half; this will cause - // proper averaging of neighboring pixels, and will typically (for the kinds - // of screen sizes used by this utility method in the layout editor) take - // about 3-4 iterations to get the result since we are logarithmically reducing - // the size. Besides, each successive pass in operating on much fewer pixels - // (a reduction of 4 in each pass). - // - // However, we may not be resizing to a size that can be reached exactly by - // successively diving in half. Therefore, once we're within a factor of 2 of - // the final size, we can do a resize to the exact target size. - // However, we can get even better results if we perform this final resize - // up front. Let's say we're going from width 1000 to a destination width of 85. - // The first approach would cause a resize from 1000 to 500 to 250 to 125, and - // then a resize from 125 to 85. That last resize can distort/blur a lot. - // Instead, we can start with the destination width, 85, and double it - // successfully until we're close to the initial size: 85, then 170, - // then 340, and finally 680. (The next one, 1360, is larger than 1000). - // So, now we *start* the thumbnail operation by resizing from width 1000 to - // width 680, which will preserve a lot of visual details such as text. - // Then we can successively resize the image in half, 680 to 340 to 170 to 85. - // We end up with the expected final size, but we've been doing an exact - // divide-in-half resizing operation at the end so there is less distortion. - - - int iterations = 0; // Number of halving operations to perform after the initial resize - int nearestWidth = destWidth; // Width closest to source width that = 2^x, x is integer - int nearestHeight = destHeight; - while (nearestWidth < sourceWidth / 2) { - nearestWidth *= 2; - nearestHeight *= 2; - iterations++; - } - - // If we're supposed to add in margins, we need to do it in the initial resizing - // operation if we don't have any subsequent resizing operations. - if (iterations == 0) { - nearestWidth += rightMargin; - nearestHeight += bottomMargin; - } - - BufferedImage scaled = new BufferedImage(nearestWidth, nearestHeight, imageType); - Graphics2D g2 = scaled.createGraphics(); - g2.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR); - g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY); - g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); - g2.drawImage(source, 0, 0, nearestWidth, nearestHeight, - 0, 0, sourceWidth, sourceHeight, null); - g2.dispose(); - - sourceWidth = nearestWidth; - sourceHeight = nearestHeight; - source = scaled; - - for (int iteration = iterations - 1; iteration >= 0; iteration--) { - int halfWidth = sourceWidth / 2; - int halfHeight = sourceHeight / 2; - if (iteration == 0) { // Last iteration: Add margins in final image - scaled = new BufferedImage(halfWidth + rightMargin, halfHeight + bottomMargin, - imageType); - } else { - scaled = new BufferedImage(halfWidth, halfHeight, imageType); - } - g2 = scaled.createGraphics(); - g2.setRenderingHint(KEY_INTERPOLATION,VALUE_INTERPOLATION_BILINEAR); - g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY); - g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); - g2.drawImage(source, 0, 0, - halfWidth, halfHeight, 0, 0, - sourceWidth, sourceHeight, - null); - g2.dispose(); - - sourceWidth = halfWidth; - sourceHeight = halfHeight; - source = scaled; - iterations--; - } - return scaled; - } - } - - /** - * Returns true if the given file path points to an image file recognized by - * Android. See http://developer.android.com/guide/appendix/media-formats.html - * for details. - * - * @param path the filename to be tested - * @return true if the file represents an image file - */ - public static boolean hasImageExtension(String path) { - return endsWithIgnoreCase(path, DOT_PNG) - || endsWithIgnoreCase(path, DOT_9PNG) - || endsWithIgnoreCase(path, DOT_GIF) - || endsWithIgnoreCase(path, DOT_JPG) - || endsWithIgnoreCase(path, DOT_BMP); - } - - /** - * Creates a new image of the given size filled with the given color - * - * @param width the width of the image - * @param height the height of the image - * @param color the color of the image - * @return a new image of the given size filled with the given color - */ - public static BufferedImage createColoredImage(int width, int height, RGB color) { - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - Graphics g = image.getGraphics(); - g.setColor(new Color(color.red, color.green, color.blue)); - g.fillRect(0, 0, image.getWidth(), image.getHeight()); - g.dispose(); - return image; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java deleted file mode 100644 index 7bab914e5..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java +++ /dev/null @@ -1,1111 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.ATTR_LAYOUT; -import static com.android.SdkConstants.EXT_XML; -import static com.android.SdkConstants.FD_RESOURCES; -import static com.android.SdkConstants.FD_RES_LAYOUT; -import static com.android.SdkConstants.TOOLS_URI; -import static com.android.SdkConstants.VIEW_FRAGMENT; -import static com.android.SdkConstants.VIEW_INCLUDE; -import static com.android.ide.eclipse.adt.AdtConstants.WS_LAYOUTS; -import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; -import static com.android.resources.ResourceType.LAYOUT; -import static org.eclipse.core.resources.IResourceDelta.ADDED; -import static org.eclipse.core.resources.IResourceDelta.CHANGED; -import static org.eclipse.core.resources.IResourceDelta.CONTENT; -import static org.eclipse.core.resources.IResourceDelta.REMOVED; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.ide.common.resources.ResourceFile; -import com.android.ide.common.resources.ResourceFolder; -import com.android.ide.common.resources.ResourceItem; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager.IResourceListener; -import com.android.ide.eclipse.adt.io.IFileWrapper; -import com.android.io.IAbstractFile; -import com.android.resources.ResourceType; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.swt.widgets.Display; -import org.eclipse.wst.sse.core.StructuredModelManager; -import org.eclipse.wst.sse.core.internal.provisional.IModelManager; -import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; -import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * The include finder finds other XML files that are including a given XML file, and does - * so efficiently (caching results across IDE sessions etc). - */ -@SuppressWarnings("restriction") // XML model -public class IncludeFinder { - /** Qualified name for the per-project persistent property include-map */ - private final static QualifiedName CONFIG_INCLUDES = new QualifiedName(AdtPlugin.PLUGIN_ID, - "includes");//$NON-NLS-1$ - - /** - * Qualified name for the per-project non-persistent property storing the - * {@link IncludeFinder} for this project - */ - private final static QualifiedName INCLUDE_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID, - "includefinder"); //$NON-NLS-1$ - - /** Project that the include finder locates includes for */ - private final IProject mProject; - - /** Map from a layout resource name to a set of layouts included by the given resource */ - private Map<String, List<String>> mIncludes = null; - - /** - * Reverse map of {@link #mIncludes}; points to other layouts that are including a - * given layouts - */ - private Map<String, List<String>> mIncludedBy = null; - - /** Flag set during a refresh; ignore updates when this is true */ - private static boolean sRefreshing; - - /** Global (cross-project) resource listener */ - private static ResourceListener sListener; - - /** - * Constructs an {@link IncludeFinder} for the given project. Don't use this method; - * use the {@link #get} factory method instead. - * - * @param project project to create an {@link IncludeFinder} for - */ - private IncludeFinder(IProject project) { - mProject = project; - } - - /** - * Returns the {@link IncludeFinder} for the given project - * - * @param project the project the finder is associated with - * @return an {@link IncludeFinder} for the given project, never null - */ - @NonNull - public static IncludeFinder get(IProject project) { - IncludeFinder finder = null; - try { - finder = (IncludeFinder) project.getSessionProperty(INCLUDE_FINDER); - } catch (CoreException e) { - // Not a problem; we will just create a new one - } - - if (finder == null) { - finder = new IncludeFinder(project); - try { - project.setSessionProperty(INCLUDE_FINDER, finder); - } catch (CoreException e) { - AdtPlugin.log(e, "Can't store IncludeFinder"); - } - } - - return finder; - } - - /** - * Returns a list of resource names that are included by the given resource - * - * @param includer the resource name to return included layouts for - * @return the layouts included by the given resource - */ - private List<String> getIncludesFrom(String includer) { - ensureInitialized(); - - return mIncludes.get(includer); - } - - /** - * Gets the list of all other layouts that are including the given layout. - * - * @param included the file that is included - * @return the files that are including the given file, or null or empty - */ - @Nullable - public List<Reference> getIncludedBy(IResource included) { - ensureInitialized(); - String mapKey = getMapKey(included); - List<String> result = mIncludedBy.get(mapKey); - if (result == null) { - String name = getResourceName(included); - if (!name.equals(mapKey)) { - result = mIncludedBy.get(name); - } - } - - if (result != null && result.size() > 0) { - List<Reference> references = new ArrayList<Reference>(result.size()); - for (String s : result) { - references.add(new Reference(mProject, s)); - } - return references; - } else { - return null; - } - } - - /** - * Returns true if the given resource is included from some other layout in the - * project - * - * @param included the resource to check - * @return true if the file is included by some other layout - */ - public boolean isIncluded(IResource included) { - ensureInitialized(); - String mapKey = getMapKey(included); - List<String> result = mIncludedBy.get(mapKey); - if (result == null) { - String name = getResourceName(included); - if (!name.equals(mapKey)) { - result = mIncludedBy.get(name); - } - } - - return result != null && result.size() > 0; - } - - @VisibleForTesting - /* package */ List<String> getIncludedBy(String included) { - ensureInitialized(); - return mIncludedBy.get(included); - } - - /** Initialize the inclusion data structures, if not already done */ - private void ensureInitialized() { - if (mIncludes == null) { - // Initialize - if (!readSettings()) { - // Couldn't read settings: probably the first time this code is running - // so there is no known data about includes. - - // Yes, these should be multimaps! If we start using Guava replace - // these with multimaps. - mIncludes = new HashMap<String, List<String>>(); - mIncludedBy = new HashMap<String, List<String>>(); - - scanProject(); - saveSettings(); - } - } - } - - // ----- Persistence ----- - - /** - * Create a String serialization of the includes map. The map attempts to be compact; - * it strips out the @layout/ prefix, and eliminates the values for empty string - * values. The map can be restored by calling {@link #decodeMap}. The encoded String - * will have sorted keys. - * - * @param map the map to be serialized - * @return a serialization (never null) of the given map - */ - @VisibleForTesting - public static String encodeMap(Map<String, List<String>> map) { - StringBuilder sb = new StringBuilder(); - - if (map != null) { - // Process the keys in sorted order rather than just - // iterating over the entry set to ensure stable output - List<String> keys = new ArrayList<String>(map.keySet()); - Collections.sort(keys); - for (String key : keys) { - List<String> values = map.get(key); - - if (sb.length() > 0) { - sb.append(','); - } - sb.append(key); - if (values.size() > 0) { - sb.append('=').append('>'); - sb.append('{'); - boolean first = true; - for (String value : values) { - if (first) { - first = false; - } else { - sb.append(','); - } - sb.append(value); - } - sb.append('}'); - } - } - } - - return sb.toString(); - } - - /** - * Decodes the encoding (produced by {@link #encodeMap}) back into the original map, - * modulo any key sorting differences. - * - * @param encoded an encoding of a map created by {@link #encodeMap} - * @return a map corresponding to the encoded values, never null - */ - @VisibleForTesting - public static Map<String, List<String>> decodeMap(String encoded) { - HashMap<String, List<String>> map = new HashMap<String, List<String>>(); - - if (encoded.length() > 0) { - int i = 0; - int end = encoded.length(); - - while (i < end) { - - // Find key range - int keyBegin = i; - int keyEnd = i; - while (i < end) { - char c = encoded.charAt(i); - if (c == ',') { - break; - } else if (c == '=') { - i += 2; // Skip => - break; - } - i++; - keyEnd = i; - } - - List<String> values = new ArrayList<String>(); - // Find values - if (i < end && encoded.charAt(i) == '{') { - i++; - while (i < end) { - int valueBegin = i; - int valueEnd = i; - char c = 0; - while (i < end) { - c = encoded.charAt(i); - if (c == ',' || c == '}') { - valueEnd = i; - break; - } - i++; - } - if (valueEnd > valueBegin) { - values.add(encoded.substring(valueBegin, valueEnd)); - } - - if (c == '}') { - if (i < end-1 && encoded.charAt(i+1) == ',') { - i++; - } - break; - } - assert c == ','; - i++; - } - } - - String key = encoded.substring(keyBegin, keyEnd); - map.put(key, values); - i++; - } - } - - return map; - } - - /** - * Stores the settings in the persistent project storage. - */ - private void saveSettings() { - // Serialize the mIncludes map into a compact String. The mIncludedBy map can be - // inferred from it. - String encoded = encodeMap(mIncludes); - - try { - if (encoded.length() >= 2048) { - // The maximum length of a setting key is 2KB, according to the javadoc - // for the project class. It's unlikely that we'll - // hit this -- even with an average layout root name of 20 characters - // we can still store over a hundred names. But JUST IN CASE we run - // into this, we'll clear out the key in this name which means that the - // information will need to be recomputed in the next IDE session. - mProject.setPersistentProperty(CONFIG_INCLUDES, null); - } else { - String existing = mProject.getPersistentProperty(CONFIG_INCLUDES); - if (!encoded.equals(existing)) { - mProject.setPersistentProperty(CONFIG_INCLUDES, encoded); - } - } - } catch (CoreException e) { - AdtPlugin.log(e, "Can't store include settings"); - } - } - - /** - * Reads previously stored settings from the persistent project storage - * - * @return true iff settings were restored from the project - */ - private boolean readSettings() { - try { - String encoded = mProject.getPersistentProperty(CONFIG_INCLUDES); - if (encoded != null) { - mIncludes = decodeMap(encoded); - - // Set up a reverse map, pointing from included files to the files that - // included them - mIncludedBy = new HashMap<String, List<String>>(2 * mIncludes.size()); - for (Map.Entry<String, List<String>> entry : mIncludes.entrySet()) { - // File containing the <include> - String includer = entry.getKey(); - // Files being <include>'ed by the above file - List<String> included = entry.getValue(); - setIncludedBy(includer, included); - } - - return true; - } - } catch (CoreException e) { - AdtPlugin.log(e, "Can't read include settings"); - } - - return false; - } - - // ----- File scanning ----- - - /** - * Scan the whole project for XML layout resources that are performing includes. - */ - private void scanProject() { - ProjectResources resources = ResourceManager.getInstance().getProjectResources(mProject); - if (resources != null) { - Collection<ResourceItem> layouts = resources.getResourceItemsOfType(LAYOUT); - for (ResourceItem layout : layouts) { - List<ResourceFile> sources = layout.getSourceFileList(); - for (ResourceFile source : sources) { - updateFileIncludes(source, false); - } - } - - return; - } - } - - /** - * Scans the given {@link ResourceFile} and if it is a layout resource, updates the - * includes in it. - * - * @param resourceFile the {@link ResourceFile} to be scanned for includes (doesn't - * have to be only layout XML files; this method will filter the type) - * @param singleUpdate true if this is a single file being updated, false otherwise - * (e.g. during initial project scanning) - * @return true if we updated the includes for the resource file - */ - private boolean updateFileIncludes(ResourceFile resourceFile, boolean singleUpdate) { - Collection<ResourceType> resourceTypes = resourceFile.getResourceTypes(); - for (ResourceType type : resourceTypes) { - if (type == ResourceType.LAYOUT) { - ensureInitialized(); - - List<String> includes = Collections.emptyList(); - if (resourceFile.getFile() instanceof IFileWrapper) { - IFile file = ((IFileWrapper) resourceFile.getFile()).getIFile(); - - // See if we have an existing XML model for this file; if so, we can - // just look directly at the parse tree - boolean hadXmlModel = false; - IStructuredModel model = null; - try { - IModelManager modelManager = StructuredModelManager.getModelManager(); - model = modelManager.getExistingModelForRead(file); - if (model instanceof IDOMModel) { - IDOMModel domModel = (IDOMModel) model; - Document document = domModel.getDocument(); - includes = findIncludesInDocument(document); - hadXmlModel = true; - } - } finally { - if (model != null) { - model.releaseFromRead(); - } - } - - // If no XML model we have to read the XML contents and (possibly) parse it. - // The actual file may not exist anymore (e.g. when deleting a layout file - // or when the workspace is out of sync.) - if (!hadXmlModel) { - String xml = AdtPlugin.readFile(file); - if (xml != null) { - includes = findIncludes(xml); - } - } - } else { - String xml = AdtPlugin.readFile(resourceFile); - if (xml != null) { - includes = findIncludes(xml); - } - } - - String key = getMapKey(resourceFile); - if (includes.equals(getIncludesFrom(key))) { - // Common case -- so avoid doing settings flush etc - return false; - } - - boolean detectCycles = singleUpdate; - setIncluded(key, includes, detectCycles); - - if (singleUpdate) { - saveSettings(); - } - - return true; - } - } - - return false; - } - - /** - * Finds the list of includes in the given XML content. It attempts quickly return - * empty if the file does not include any include tags; it does this by only parsing - * if it detects the string <include in the file. - */ - @VisibleForTesting - @NonNull - static List<String> findIncludes(@NonNull String xml) { - int index = xml.indexOf(ATTR_LAYOUT); - if (index != -1) { - return findIncludesInXml(xml); - } - - return Collections.emptyList(); - } - - /** - * Parses the given XML content and extracts all the included URLs and returns them - * - * @param xml layout XML content to be parsed for includes - * @return a list of included urls, or null - */ - @VisibleForTesting - @NonNull - static List<String> findIncludesInXml(@NonNull String xml) { - Document document = DomUtilities.parseDocument(xml, false /*logParserErrors*/); - if (document != null) { - return findIncludesInDocument(document); - } - - return Collections.emptyList(); - } - - /** Searches the given DOM document and returns the list of includes, if any */ - @NonNull - private static List<String> findIncludesInDocument(@NonNull Document document) { - List<String> includes = findIncludesInDocument(document, null); - if (includes == null) { - includes = Collections.emptyList(); - } - return includes; - } - - @Nullable - private static List<String> findIncludesInDocument(@NonNull Node node, - @Nullable List<String> urls) { - if (node.getNodeType() == Node.ELEMENT_NODE) { - String tag = node.getNodeName(); - boolean isInclude = tag.equals(VIEW_INCLUDE); - boolean isFragment = tag.equals(VIEW_FRAGMENT); - if (isInclude || isFragment) { - Element element = (Element) node; - String url; - if (isInclude) { - url = element.getAttribute(ATTR_LAYOUT); - } else { - url = element.getAttributeNS(TOOLS_URI, ATTR_LAYOUT); - } - if (url.length() > 0) { - String resourceName = urlToLocalResource(url); - if (resourceName != null) { - if (urls == null) { - urls = new ArrayList<String>(); - } - urls.add(resourceName); - } - } - - } - } - - NodeList children = node.getChildNodes(); - for (int i = 0, n = children.getLength(); i < n; i++) { - urls = findIncludesInDocument(children.item(i), urls); - } - - return urls; - } - - - /** - * Returns the layout URL to a local resource name (provided the URL is a local - * resource, not something in @android etc.) Returns null otherwise. - */ - private static String urlToLocalResource(String url) { - if (!url.startsWith("@")) { //$NON-NLS-1$ - return null; - } - int typeEnd = url.indexOf('/', 1); - if (typeEnd == -1) { - return null; - } - int nameBegin = typeEnd + 1; - int typeBegin = 1; - int colon = url.lastIndexOf(':', typeEnd); - if (colon != -1) { - String packageName = url.substring(typeBegin, colon); - if ("android".equals(packageName)) { //$NON-NLS-1$ - // Don't want to point to non-local resources - return null; - } - - typeBegin = colon + 1; - assert "layout".equals(url.substring(typeBegin, typeEnd)); //$NON-NLS-1$ - } - - return url.substring(nameBegin); - } - - /** - * Record the list of included layouts from the given layout - * - * @param includer the layout including other layouts - * @param included the layouts that were included by the including layout - * @param detectCycles if true, check for cycles and report them as project errors - */ - @VisibleForTesting - /* package */ void setIncluded(String includer, List<String> included, boolean detectCycles) { - // Remove previously linked inverse mappings - List<String> oldIncludes = mIncludes.get(includer); - if (oldIncludes != null && oldIncludes.size() > 0) { - for (String includee : oldIncludes) { - List<String> includers = mIncludedBy.get(includee); - if (includers != null) { - includers.remove(includer); - } - } - } - - mIncludes.put(includer, included); - // Reverse mapping: for included items, point back to including file - setIncludedBy(includer, included); - - if (detectCycles) { - detectCycles(includer); - } - } - - /** Record the list of included layouts from the given layout */ - private void setIncludedBy(String includer, List<String> included) { - for (String target : included) { - List<String> list = mIncludedBy.get(target); - if (list == null) { - list = new ArrayList<String>(2); // We don't expect many includes - mIncludedBy.put(target, list); - } - if (!list.contains(includer)) { - list.add(includer); - } - } - } - - /** Start listening on project resources */ - public static void start() { - assert sListener == null; - sListener = new ResourceListener(); - ResourceManager.getInstance().addListener(sListener); - } - - /** Stop listening on project resources */ - public static void stop() { - assert sListener != null; - ResourceManager.getInstance().addListener(sListener); - } - - private static String getMapKey(ResourceFile resourceFile) { - IAbstractFile file = resourceFile.getFile(); - String name = file.getName(); - String folderName = file.getParentFolder().getName(); - return getMapKey(folderName, name); - } - - private static String getMapKey(IResource resourceFile) { - String folderName = resourceFile.getParent().getName(); - String name = resourceFile.getName(); - return getMapKey(folderName, name); - } - - private static String getResourceName(IResource resourceFile) { - String name = resourceFile.getName(); - int baseEnd = name.length() - EXT_XML.length() - 1; // -1: the dot - if (baseEnd > 0) { - name = name.substring(0, baseEnd); - } - - return name; - } - - private static String getMapKey(String folderName, String name) { - int baseEnd = name.length() - EXT_XML.length() - 1; // -1: the dot - if (baseEnd > 0) { - name = name.substring(0, baseEnd); - } - - // Create a map key for the given resource file - // This will map - // /res/layout/foo.xml => "foo" - // /res/layout-land/foo.xml => "-land/foo" - - if (FD_RES_LAYOUT.equals(folderName)) { - // Normal case -- keep just the basename - return name; - } else { - // Store the relative path from res/ on down, so - // /res/layout-land/foo.xml becomes "layout-land/foo" - //if (folderName.startsWith(FD_LAYOUT)) { - // folderName = folderName.substring(FD_LAYOUT.length()); - //} - - return folderName + WS_SEP + name; - } - } - - /** Listener of resource file saves, used to update layout inclusion data structures */ - private static class ResourceListener implements IResourceListener { - @Override - public void fileChanged(IProject project, ResourceFile file, int eventType) { - if (sRefreshing) { - return; - } - - if ((eventType & (CHANGED | ADDED | REMOVED | CONTENT)) == 0) { - return; - } - - IncludeFinder finder = get(project); - if (finder != null) { - if (finder.updateFileIncludes(file, true)) { - finder.saveSettings(); - } - } - } - - @Override - public void folderChanged(IProject project, ResourceFolder folder, int eventType) { - // We only care about layout resource files - } - } - - // ----- Cycle detection ----- - - private void detectCycles(String from) { - // Perform DFS on the include graph and look for a cycle; if we find one, produce - // a chain of includes on the way back to show to the user - if (mIncludes.size() > 0) { - Set<String> visiting = new HashSet<String>(mIncludes.size()); - String chain = dfs(from, visiting); - if (chain != null) { - addError(from, chain); - } else { - // Is there an existing error for us to clean up? - removeErrors(from); - } - } - } - - /** Format to chain include cycles in: a=>b=>c=>d etc */ - private final String CHAIN_FORMAT = "%1$s=>%2$s"; //$NON-NLS-1$ - - private String dfs(String from, Set<String> visiting) { - visiting.add(from); - - List<String> includes = mIncludes.get(from); - if (includes != null && includes.size() > 0) { - for (String include : includes) { - if (visiting.contains(include)) { - return String.format(CHAIN_FORMAT, from, include); - } - String chain = dfs(include, visiting); - if (chain != null) { - return String.format(CHAIN_FORMAT, from, chain); - } - } - } - - visiting.remove(from); - - return null; - } - - private void removeErrors(String from) { - final IResource resource = findResource(from); - if (resource != null) { - try { - final String markerId = IMarker.PROBLEM; - - IMarker[] markers = resource.findMarkers(markerId, true, IResource.DEPTH_ZERO); - - for (final IMarker marker : markers) { - String tmpMsg = marker.getAttribute(IMarker.MESSAGE, null); - if (tmpMsg == null || tmpMsg.startsWith(MESSAGE)) { - // Remove - runLater(new Runnable() { - @Override - public void run() { - try { - sRefreshing = true; - marker.delete(); - } catch (CoreException e) { - AdtPlugin.log(e, "Can't delete problem marker"); - } finally { - sRefreshing = false; - } - } - }); - } - } - } catch (CoreException e) { - // if we couldn't get the markers, then we just mark the file again - // (since markerAlreadyExists is initialized to false, we do nothing) - } - } - } - - /** Error message for cycles */ - private static final String MESSAGE = "Found cyclical <include> chain"; - - private void addError(String from, String chain) { - final IResource resource = findResource(from); - if (resource != null) { - final String markerId = IMarker.PROBLEM; - final String message = String.format("%1$s: %2$s", MESSAGE, chain); - final int lineNumber = 1; - final int severity = IMarker.SEVERITY_ERROR; - - // check if there's a similar marker already, since aapt is launched twice - boolean markerAlreadyExists = false; - try { - IMarker[] markers = resource.findMarkers(markerId, true, IResource.DEPTH_ZERO); - - for (IMarker marker : markers) { - int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1); - if (tmpLine != lineNumber) { - break; - } - - int tmpSeverity = marker.getAttribute(IMarker.SEVERITY, -1); - if (tmpSeverity != severity) { - break; - } - - String tmpMsg = marker.getAttribute(IMarker.MESSAGE, null); - if (tmpMsg == null || tmpMsg.equals(message) == false) { - break; - } - - // if we're here, all the marker attributes are equals, we found it - // and exit - markerAlreadyExists = true; - break; - } - - } catch (CoreException e) { - // if we couldn't get the markers, then we just mark the file again - // (since markerAlreadyExists is initialized to false, we do nothing) - } - - if (!markerAlreadyExists) { - runLater(new Runnable() { - @Override - public void run() { - try { - sRefreshing = true; - - // Adding a resource will force a refresh on the file; - // ignore these updates - BaseProjectHelper.markResource(resource, markerId, message, lineNumber, - severity); - } finally { - sRefreshing = false; - } - } - }); - } - } - } - - // FIXME: Find more standard Eclipse way to do this. - // We need to run marker registration/deletion "later", because when the include - // scanning is running it's in the middle of resource notification, so the IDE - // throws an exception - private static void runLater(Runnable runnable) { - Display display = Display.findDisplay(Thread.currentThread()); - if (display != null) { - display.asyncExec(runnable); - } else { - AdtPlugin.log(IStatus.WARNING, "Could not find display"); - } - } - - /** - * Finds the project resource for the given layout path - * - * @param from the resource name - * @return the {@link IResource}, or null if not found - */ - private IResource findResource(String from) { - final IResource resource = mProject.findMember(WS_LAYOUTS + WS_SEP + from + '.' + EXT_XML); - return resource; - } - - /** - * Creates a blank, project-less {@link IncludeFinder} <b>for use by unit tests - * only</b> - */ - @VisibleForTesting - /* package */ static IncludeFinder create() { - IncludeFinder finder = new IncludeFinder(null); - finder.mIncludes = new HashMap<String, List<String>>(); - finder.mIncludedBy = new HashMap<String, List<String>>(); - return finder; - } - - /** A reference to a particular file in the project */ - public static class Reference { - /** The unique id referencing the file, such as (for res/layout-land/main.xml) - * "layout-land/main") */ - private final String mId; - - /** The project containing the file */ - private final IProject mProject; - - /** The resource name of the file, such as (for res/layout/main.xml) "main" */ - private String mName; - - /** Creates a new include reference */ - private Reference(IProject project, String id) { - super(); - mProject = project; - mId = id; - } - - /** - * Returns the id identifying the given file within the project - * - * @return the id identifying the given file within the project - */ - public String getId() { - return mId; - } - - /** - * Returns the {@link IFile} in the project for the given file. May return null if - * there is an error in locating the file or if the file no longer exists. - * - * @return the project file, or null - */ - public IFile getFile() { - String reference = mId; - if (!reference.contains(WS_SEP)) { - reference = FD_RES_LAYOUT + WS_SEP + reference; - } - - String projectPath = FD_RESOURCES + WS_SEP + reference + '.' + EXT_XML; - IResource member = mProject.findMember(projectPath); - if (member instanceof IFile) { - return (IFile) member; - } - - return null; - } - - /** - * Returns a description of this reference, suitable to be shown to the user - * - * @return a display name for the reference - */ - public String getDisplayName() { - // The ID is deliberately kept in a pretty user-readable format but we could - // consider prepending layout/ on ids that don't have it (to make the display - // more uniform) or ripping out all layout[-constraint] prefixes out and - // instead prepending @ etc. - return mId; - } - - /** - * Returns the name of the reference, suitable for resource lookup. For example, - * for "res/layout/main.xml", as well as for "res/layout-land/main.xml", this - * would be "main". - * - * @return the resource name of the reference - */ - public String getName() { - if (mName == null) { - mName = mId; - int index = mName.lastIndexOf(WS_SEP); - if (index != -1) { - mName = mName.substring(index + 1); - } - } - - return mName; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((mId == null) ? 0 : mId.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Reference other = (Reference) obj; - if (mId == null) { - if (other.mId != null) - return false; - } else if (!mId.equals(other.mId)) - return false; - return true; - } - - @Override - public String toString() { - return "Reference [getId()=" + getId() //$NON-NLS-1$ - + ", getDisplayName()=" + getDisplayName() //$NON-NLS-1$ - + ", getName()=" + getName() //$NON-NLS-1$ - + ", getFile()=" + getFile() + "]"; //$NON-NLS-1$ - } - - /** - * Creates a reference to the given file - * - * @param file the file to create a reference for - * @return a reference to the given file - */ - public static Reference create(IFile file) { - return new Reference(file.getProject(), getMapKey(file)); - } - - /** - * Returns the resource name of this layout, such as {@code @layout/foo}. - * - * @return the resource name - */ - public String getResourceName() { - return '@' + FD_RES_LAYOUT + '/' + getName(); - } - } - - /** - * Returns a collection of layouts (expressed as resource names, such as - * {@code @layout/foo} which would be invalid includes in the given layout - * (because it would introduce a cycle) - * - * @param layout the layout file to check for cyclic dependencies from - * @return a collection of layout resources which cannot be included from - * the given layout, never null - */ - public Collection<String> getInvalidIncludes(IFile layout) { - IProject project = layout.getProject(); - Reference self = Reference.create(layout); - - // Add anyone who transitively can reach this file via includes. - LinkedList<Reference> queue = new LinkedList<Reference>(); - List<Reference> invalid = new ArrayList<Reference>(); - queue.add(self); - invalid.add(self); - Set<String> seen = new HashSet<String>(); - seen.add(self.getId()); - while (!queue.isEmpty()) { - Reference reference = queue.removeFirst(); - String refId = reference.getId(); - - // Look up both configuration specific includes as well as includes in the - // base versions - List<String> included = getIncludedBy(refId); - if (refId.indexOf('/') != -1) { - List<String> baseIncluded = getIncludedBy(reference.getName()); - if (included == null) { - included = baseIncluded; - } else if (baseIncluded != null) { - included = new ArrayList<String>(included); - included.addAll(baseIncluded); - } - } - - if (included != null && included.size() > 0) { - for (String id : included) { - if (!seen.contains(id)) { - seen.add(id); - Reference ref = new Reference(project, id); - invalid.add(ref); - queue.addLast(ref); - } - } - } - } - - List<String> result = new ArrayList<String>(); - for (Reference reference : invalid) { - result.add(reference.getResourceName()); - } - - return result; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeOverlay.java deleted file mode 100644 index 81c03edd5..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeOverlay.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.VisibleForTesting; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.Rectangle; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * The {@link IncludeOverlay} class renders masks to -partially- hide everything outside - * an included file's own content. This overlay is in use when you are editing an included - * file shown within a different file's context (e.g. "Show In > other"). - */ -public class IncludeOverlay extends Overlay { - /** Mask transparency - 0 is transparent, 255 is opaque */ - private static final int MASK_TRANSPARENCY = 160; - - /** The associated {@link LayoutCanvas}. */ - private LayoutCanvas mCanvas; - - /** - * Constructs an {@link IncludeOverlay} tied to the given canvas. - * - * @param canvas The {@link LayoutCanvas} to paint the overlay over. - */ - public IncludeOverlay(LayoutCanvas canvas) { - mCanvas = canvas; - } - - @Override - public void paint(GC gc) { - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - List<Rectangle> includedBounds = viewHierarchy.getIncludedBounds(); - if (includedBounds == null || includedBounds.size() == 0) { - // We don't support multiple included children yet. When that works, - // this code should use a BSP tree to figure out which regions to paint - // to leave holes in the mask. - return; - } - - Image image = mCanvas.getImageOverlay().getImage(); - if (image == null) { - return; - } - - int oldAlpha = gc.getAlpha(); - gc.setAlpha(MASK_TRANSPARENCY); - Color bg = gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); - gc.setBackground(bg); - - CanvasViewInfo root = viewHierarchy.getRoot(); - Rectangle whole = root.getAbsRect(); - whole = new Rectangle(whole.x, whole.y, whole.width + 1, whole.height + 1); - Collection<Rectangle> masks = subtractRectangles(whole, includedBounds); - - for (Rectangle mask : masks) { - ControlPoint topLeft = LayoutPoint.create(mCanvas, mask.x, mask.y).toControl(); - ControlPoint bottomRight = LayoutPoint.create(mCanvas, mask.x + mask.width, - mask.y + mask.height).toControl(); - int x1 = topLeft.x; - int y1 = topLeft.y; - int x2 = bottomRight.x; - int y2 = bottomRight.y; - - gc.fillRectangle(x1, y1, x2 - x1, y2 - y1); - } - - gc.setAlpha(oldAlpha); - } - - /** - * Given a Rectangle, remove holes from it (specified as a collection of Rectangles) such - * that the result is a list of rectangles that cover everything that is not a hole. - * - * @param rectangle the rectangle to subtract from - * @param holes the holes to subtract from the rectangle - * @return a list of sub rectangles that remain after subtracting out the given list of holes - */ - @VisibleForTesting - static Collection<Rectangle> subtractRectangles( - Rectangle rectangle, Collection<Rectangle> holes) { - List<Rectangle> result = new ArrayList<Rectangle>(); - result.add(rectangle); - - for (Rectangle hole : holes) { - List<Rectangle> tempResult = new ArrayList<Rectangle>(); - for (Rectangle r : result) { - if (hole.intersects(r)) { - // Clip the hole to fit the rectangle bounds - Rectangle h = hole.intersection(r); - - // Split the rectangle - - // Above (includes the NW and NE corners) - if (h.y > r.y) { - tempResult.add(new Rectangle(r.x, r.y, r.width, h.y - r.y)); - } - - // Left (not including corners) - if (h.x > r.x) { - tempResult.add(new Rectangle(r.x, h.y, h.x - r.x, h.height)); - } - - int hx2 = h.x + h.width; - int hy2 = h.y + h.height; - int rx2 = r.x + r.width; - int ry2 = r.y + r.height; - - // Below (includes the SW and SE corners) - if (hy2 < ry2) { - tempResult.add(new Rectangle(r.x, hy2, r.width, ry2 - hy2)); - } - - // Right (not including corners) - if (hx2 < rx2) { - tempResult.add(new Rectangle(hx2, h.y, rx2 - hx2, h.height)); - } - } else { - tempResult.add(r); - } - } - - result = tempResult; - } - - return result; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java deleted file mode 100644 index 1b1bd23c4..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java +++ /dev/null @@ -1,732 +0,0 @@ -/* - * 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.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_ID; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.RuleAction; -import com.android.ide.common.api.RuleAction.Choices; -import com.android.ide.common.api.RuleAction.Separator; -import com.android.ide.common.api.RuleAction.Toggle; -import com.android.ide.common.layout.BaseViewRule; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; -import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient; -import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; -import com.android.sdklib.devices.Device; -import com.android.sdklib.devices.Screen; -import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog; -import com.google.common.base.Strings; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarker; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -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.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.MenuItem; -import org.eclipse.swt.widgets.ToolBar; -import org.eclipse.swt.widgets.ToolItem; -import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; - -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Toolbar shown at the top of the layout editor, which adds a number of context-sensitive - * layout actions (as well as zooming controls on the right). - */ -public class LayoutActionBar extends Composite { - private GraphicalEditorPart mEditor; - private ToolBar mLayoutToolBar; - private ToolBar mLintToolBar; - private ToolBar mZoomToolBar; - private ToolItem mZoomRealSizeButton; - private ToolItem mZoomOutButton; - private ToolItem mZoomResetButton; - private ToolItem mZoomInButton; - private ToolItem mZoomFitButton; - private ToolItem mLintButton; - private List<RuleAction> mPrevActions; - - /** - * Creates a new {@link LayoutActionBar} and adds it to the given parent. - * - * @param parent the parent composite to add the actions bar to - * @param style the SWT style to apply - * @param editor the associated layout editor - */ - public LayoutActionBar(Composite parent, int style, GraphicalEditorPart editor) { - super(parent, style | SWT.NO_FOCUS); - mEditor = editor; - - GridLayout layout = new GridLayout(3, false); - setLayout(layout); - - mLayoutToolBar = new ToolBar(this, /*SWT.WRAP |*/ SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL); - mLayoutToolBar.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); - mZoomToolBar = createZoomControls(); - mZoomToolBar.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, false, false)); - mLintToolBar = createLintControls(); - - GridData lintData = new GridData(SWT.END, SWT.BEGINNING, false, false); - lintData.exclude = true; - mLintToolBar.setLayoutData(lintData); - } - - @Override - public void dispose() { - super.dispose(); - mPrevActions = null; - } - - /** Updates the layout contents based on the current selection */ - void updateSelection() { - NodeProxy parent = null; - LayoutCanvas canvas = mEditor.getCanvasControl(); - SelectionManager selectionManager = canvas.getSelectionManager(); - List<SelectionItem> selections = selectionManager.getSelections(); - if (selections.size() > 0) { - // TODO: better handle multi-selection -- maybe we should disable it or - // something. - // What if you select children with different parents? Of different types? - // etc. - NodeProxy node = selections.get(0).getNode(); - if (node != null && node.getParent() != null) { - parent = (NodeProxy) node.getParent(); - } - } - - if (parent == null) { - // Show the background's properties - CanvasViewInfo root = canvas.getViewHierarchy().getRoot(); - if (root == null) { - return; - } - parent = canvas.getNodeFactory().create(root); - selections = Collections.emptyList(); - } - - RulesEngine engine = mEditor.getRulesEngine(); - List<NodeProxy> selectedNodes = new ArrayList<NodeProxy>(); - for (SelectionItem item : selections) { - selectedNodes.add(item.getNode()); - } - List<RuleAction> actions = new ArrayList<RuleAction>(); - engine.callAddLayoutActions(actions, parent, selectedNodes); - - // Place actions in the correct order (the actions may come from different - // rules and should be merged properly via sorting keys) - Collections.sort(actions); - - // Add in actions for the child as well, if there is exactly one. - // These are not merged into the parent list of actions; they are appended - // at the end. - int index = -1; - String label = null; - if (selectedNodes.size() == 1) { - List<RuleAction> itemActions = new ArrayList<RuleAction>(); - NodeProxy selectedNode = selectedNodes.get(0); - engine.callAddLayoutActions(itemActions, selectedNode, null); - if (itemActions.size() > 0) { - Collections.sort(itemActions); - - if (!(itemActions.get(0) instanceof RuleAction.Separator)) { - actions.add(RuleAction.createSeparator(0)); - } - label = selectedNode.getStringAttr(ANDROID_URI, ATTR_ID); - if (label != null) { - label = BaseViewRule.stripIdPrefix(label); - index = actions.size(); - } - actions.addAll(itemActions); - } - } - - if (!updateActions(actions)) { - updateToolbar(actions, index, label); - } - mPrevActions = actions; - } - - /** Update the toolbar widgets */ - private void updateToolbar(final List<RuleAction> actions, final int labelIndex, - final String label) { - if (mLayoutToolBar == null || mLayoutToolBar.isDisposed()) { - return; - } - for (ToolItem c : mLayoutToolBar.getItems()) { - c.dispose(); - } - mLayoutToolBar.pack(); - addActions(actions, labelIndex, label); - mLayoutToolBar.pack(); - mLayoutToolBar.layout(); - } - - /** - * Attempts to update the existing toolbar actions, if the action list is - * similar to the current list. Returns false if this cannot be done and the - * contents must be replaced. - */ - private boolean updateActions(@NonNull List<RuleAction> actions) { - List<RuleAction> before = mPrevActions; - List<RuleAction> after = actions; - - if (before == null) { - return false; - } - - if (!before.equals(after) || after.size() > mLayoutToolBar.getItemCount()) { - return false; - } - - int actionIndex = 0; - for (int i = 0, max = mLayoutToolBar.getItemCount(); i < max; i++) { - ToolItem item = mLayoutToolBar.getItem(i); - int style = item.getStyle(); - Object data = item.getData(); - if (data != null) { - // One action can result in multiple toolbar items (e.g. a choice action - // can result in multiple radio buttons), so we've have to replace all of - // them with the corresponding new action - RuleAction prevAction = before.get(actionIndex); - while (prevAction != data) { - actionIndex++; - if (actionIndex == before.size()) { - return false; - } - prevAction = before.get(actionIndex); - if (prevAction == data) { - break; - } else if (!(prevAction instanceof RuleAction.Separator)) { - return false; - } - } - RuleAction newAction = after.get(actionIndex); - assert newAction.equals(prevAction); // Maybe I can do this lazily instead? - - // Update action binding to the new action - item.setData(newAction); - - // Sync button states: the checked state is not considered part of - // RuleAction equality - if ((style & SWT.CHECK) != 0) { - assert newAction instanceof Toggle; - Toggle toggle = (Toggle) newAction; - item.setSelection(toggle.isChecked()); - } else if ((style & SWT.RADIO) != 0) { - assert newAction instanceof Choices; - Choices choices = (Choices) newAction; - String current = choices.getCurrent(); - String id = (String) item.getData(ATTR_ID); - boolean selected = Strings.nullToEmpty(current).equals(id); - item.setSelection(selected); - } - } else { - // Must be a separator, or a label (which we insert for nested widgets) - assert (style & SWT.SEPARATOR) != 0 || !item.getText().isEmpty() : item; - } - } - - return true; - } - - private void addActions(List<RuleAction> actions, int labelIndex, String label) { - if (actions.size() > 0) { - // Flag used to indicate that if there are any actions -after- this, it - // should be separated from this current action (we don't unconditionally - // add a separator at the end of these groups in case there are no more - // actions at the end so that we don't have a trailing separator) - boolean needSeparator = false; - - int index = 0; - for (RuleAction action : actions) { - if (index == labelIndex) { - final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH); - button.setText(label); - needSeparator = false; - } - index++; - - if (action instanceof Separator) { - addSeparator(mLayoutToolBar); - needSeparator = false; - continue; - } else if (needSeparator) { - addSeparator(mLayoutToolBar); - needSeparator = false; - } - - if (action instanceof RuleAction.Choices) { - RuleAction.Choices choices = (Choices) action; - if (!choices.isRadio()) { - addDropdown(choices); - } else { - addSeparator(mLayoutToolBar); - addRadio(choices); - needSeparator = true; - } - } else if (action instanceof RuleAction.Toggle) { - addToggle((Toggle) action); - } else { - addPlainAction(action); - } - } - } - } - - /** Add a separator to the toolbar, unless there already is one there at the end already */ - private static void addSeparator(ToolBar toolBar) { - int n = toolBar.getItemCount(); - if (n > 0 && (toolBar.getItem(n - 1).getStyle() & SWT.SEPARATOR) == 0) { - ToolItem separator = new ToolItem(toolBar, SWT.SEPARATOR); - separator.setWidth(15); - } - } - - private void addToggle(Toggle toggle) { - final ToolItem button = new ToolItem(mLayoutToolBar, SWT.CHECK); - - URL iconUrl = toggle.getIconUrl(); - String title = toggle.getTitle(); - if (iconUrl != null) { - button.setImage(IconFactory.getInstance().getIcon(iconUrl)); - button.setToolTipText(title); - } else { - button.setText(title); - } - button.setData(toggle); - - button.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - Toggle toggle = (Toggle) button.getData(); - toggle.getCallback().action(toggle, getSelectedNodes(), - toggle.getId(), button.getSelection()); - updateSelection(); - } - }); - if (toggle.isChecked()) { - button.setSelection(true); - } - } - - private List<INode> getSelectedNodes() { - List<SelectionItem> selections = - mEditor.getCanvasControl().getSelectionManager().getSelections(); - List<INode> nodes = new ArrayList<INode>(selections.size()); - for (SelectionItem item : selections) { - nodes.add(item.getNode()); - } - - return nodes; - } - - - private void addPlainAction(RuleAction menuAction) { - final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH); - - URL iconUrl = menuAction.getIconUrl(); - String title = menuAction.getTitle(); - if (iconUrl != null) { - button.setImage(IconFactory.getInstance().getIcon(iconUrl)); - button.setToolTipText(title); - } else { - button.setText(title); - } - button.setData(menuAction); - - button.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - RuleAction menuAction = (RuleAction) button.getData(); - menuAction.getCallback().action(menuAction, getSelectedNodes(), menuAction.getId(), - false); - updateSelection(); - } - }); - } - - private void addRadio(RuleAction.Choices choices) { - List<URL> icons = choices.getIconUrls(); - List<String> titles = choices.getTitles(); - List<String> ids = choices.getIds(); - String current = choices.getCurrent() != null ? choices.getCurrent() : ""; //$NON-NLS-1$ - - assert icons != null; - assert icons.size() == titles.size(); - - for (int i = 0; i < icons.size(); i++) { - URL iconUrl = icons.get(i); - String title = titles.get(i); - final String id = ids.get(i); - final ToolItem item = new ToolItem(mLayoutToolBar, SWT.RADIO); - item.setToolTipText(title); - item.setImage(IconFactory.getInstance().getIcon(iconUrl)); - item.setData(choices); - item.setData(ATTR_ID, id); - item.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - if (item.getSelection()) { - RuleAction.Choices choices = (Choices) item.getData(); - choices.getCallback().action(choices, getSelectedNodes(), id, null); - updateSelection(); - } - } - }); - boolean selected = current.equals(id); - if (selected) { - item.setSelection(true); - } - } - } - - private void addDropdown(RuleAction.Choices choices) { - final ToolItem combo = new ToolItem(mLayoutToolBar, SWT.DROP_DOWN); - URL iconUrl = choices.getIconUrl(); - if (iconUrl != null) { - combo.setImage(IconFactory.getInstance().getIcon(iconUrl)); - combo.setToolTipText(choices.getTitle()); - } else { - combo.setText(choices.getTitle()); - } - combo.setData(choices); - - Listener menuListener = new Listener() { - @Override - public void handleEvent(Event event) { - Menu menu = new Menu(mLayoutToolBar.getShell(), SWT.POP_UP); - RuleAction.Choices choices = (Choices) combo.getData(); - List<URL> icons = choices.getIconUrls(); - List<String> titles = choices.getTitles(); - List<String> ids = choices.getIds(); - String current = choices.getCurrent() != null ? choices.getCurrent() : ""; //$NON-NLS-1$ - - for (int i = 0; i < titles.size(); i++) { - String title = titles.get(i); - final String id = ids.get(i); - URL itemIconUrl = icons != null && icons.size() > 0 ? icons.get(i) : null; - MenuItem item = new MenuItem(menu, SWT.CHECK); - item.setText(title); - if (itemIconUrl != null) { - Image itemIcon = IconFactory.getInstance().getIcon(itemIconUrl); - item.setImage(itemIcon); - } - - boolean selected = id.equals(current); - if (selected) { - item.setSelection(true); - } - - item.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - RuleAction.Choices choices = (Choices) combo.getData(); - choices.getCallback().action(choices, getSelectedNodes(), id, null); - updateSelection(); - } - }); - } - - Rectangle bounds = combo.getBounds(); - Point location = new Point(bounds.x, bounds.y + bounds.height); - location = combo.getParent().toDisplay(location); - menu.setLocation(location.x, location.y); - menu.setVisible(true); - } - }; - combo.addListener(SWT.Selection, menuListener); - } - - // ---- Zoom Controls ---- - - @SuppressWarnings("unused") // SWT constructors have side effects, they are not unused - private ToolBar createZoomControls() { - ToolBar toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL); - - IconFactory iconFactory = IconFactory.getInstance(); - mZoomRealSizeButton = new ToolItem(toolBar, SWT.CHECK); - mZoomRealSizeButton.setToolTipText("Emulate Real Size"); - mZoomRealSizeButton.setImage(iconFactory.getIcon("zoomreal")); //$NON-NLS-1$); - mZoomRealSizeButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - boolean newState = mZoomRealSizeButton.getSelection(); - if (rescaleToReal(newState)) { - mZoomOutButton.setEnabled(!newState); - mZoomResetButton.setEnabled(!newState); - mZoomInButton.setEnabled(!newState); - mZoomFitButton.setEnabled(!newState); - } else { - mZoomRealSizeButton.setSelection(!newState); - } - } - }); - - mZoomFitButton = new ToolItem(toolBar, SWT.PUSH); - mZoomFitButton.setToolTipText("Zoom to Fit (0)"); - mZoomFitButton.setImage(iconFactory.getIcon("zoomfit")); //$NON-NLS-1$); - mZoomFitButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - rescaleToFit(true); - } - }); - - mZoomResetButton = new ToolItem(toolBar, SWT.PUSH); - mZoomResetButton.setToolTipText("Reset Zoom to 100% (1)"); - mZoomResetButton.setImage(iconFactory.getIcon("zoom100")); //$NON-NLS-1$); - mZoomResetButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - resetScale(); - } - }); - - // Group zoom in/out separately - new ToolItem(toolBar, SWT.SEPARATOR); - - mZoomOutButton = new ToolItem(toolBar, SWT.PUSH); - mZoomOutButton.setToolTipText("Zoom Out (-)"); - mZoomOutButton.setImage(iconFactory.getIcon("zoomminus")); //$NON-NLS-1$); - mZoomOutButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - rescale(-1); - } - }); - - mZoomInButton = new ToolItem(toolBar, SWT.PUSH); - mZoomInButton.setToolTipText("Zoom In (+)"); - mZoomInButton.setImage(iconFactory.getIcon("zoomplus")); //$NON-NLS-1$); - mZoomInButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - rescale(+1); - } - }); - - return toolBar; - } - - @SuppressWarnings("unused") // SWT constructors have side effects, they are not unused - private ToolBar createLintControls() { - ToolBar toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL); - - // Separate from adjacent toolbar - new ToolItem(toolBar, SWT.SEPARATOR); - - ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); - mLintButton = new ToolItem(toolBar, SWT.PUSH); - mLintButton.setToolTipText("Show Lint Warnings for this Layout"); - mLintButton.setImage(sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK)); - mLintButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - CommonXmlEditor editor = mEditor.getEditorDelegate().getEditor(); - IFile file = editor.getInputFile(); - if (file != null) { - EclipseLintClient.showErrors(getShell(), file, editor); - } - } - }); - - return toolBar; - } - - /** - * Updates the lint indicator state in the given layout editor - */ - public void updateErrorIndicator() { - updateErrorIndicator(mEditor.getEditedFile()); - } - - /** - * Updates the lint indicator state for the given file - * - * @param file the file to show the indicator status for - */ - public void updateErrorIndicator(IFile file) { - IMarker[] markers = EclipseLintClient.getMarkers(file); - updateErrorIndicator(markers.length); - } - - /** - * Sets whether the action bar should show the "lint warnings" button - * - * @param hasLintWarnings whether there are lint errors to be shown - */ - private void updateErrorIndicator(final int markerCount) { - Display display = getDisplay(); - if (display.getThread() != Thread.currentThread()) { - display.asyncExec(new Runnable() { - @Override - public void run() { - if (!isDisposed()) { - updateErrorIndicator(markerCount); - } - } - }); - return; - } - - GridData layoutData = (GridData) mLintToolBar.getLayoutData(); - Integer existing = (Integer) mLintToolBar.getData(); - Integer current = Integer.valueOf(markerCount); - if (!current.equals(existing)) { - mLintToolBar.setData(current); - boolean layout = false; - boolean hasLintWarnings = markerCount > 0 && AdtPrefs.getPrefs().isLintOnSave(); - if (layoutData.exclude == hasLintWarnings) { - layoutData.exclude = !hasLintWarnings; - mLintToolBar.setVisible(hasLintWarnings); - layout = true; - } - if (markerCount > 0) { - String iconName = ""; - switch (markerCount) { - case 1: iconName = "lint1"; break; //$NON-NLS-1$ - case 2: iconName = "lint2"; break; //$NON-NLS-1$ - case 3: iconName = "lint3"; break; //$NON-NLS-1$ - case 4: iconName = "lint4"; break; //$NON-NLS-1$ - case 5: iconName = "lint5"; break; //$NON-NLS-1$ - case 6: iconName = "lint6"; break; //$NON-NLS-1$ - case 7: iconName = "lint7"; break; //$NON-NLS-1$ - case 8: iconName = "lint8"; break; //$NON-NLS-1$ - case 9: iconName = "lint9"; break; //$NON-NLS-1$ - default: iconName = "lint9p"; break;//$NON-NLS-1$ - } - mLintButton.setImage(IconFactory.getInstance().getIcon(iconName)); - } - if (layout) { - layout(); - } - redraw(); - } - } - - /** - * Returns true if zooming in/out/to-fit/etc is allowed (which is not the case while - * emulating real size) - * - * @return true if zooming is allowed - */ - boolean isZoomingAllowed() { - return mZoomInButton.isEnabled(); - } - - boolean isZoomingRealSize() { - return mZoomRealSizeButton.getSelection(); - } - - /** - * Rescales canvas. - * @param direction +1 for zoom in, -1 for zoom out - */ - void rescale(int direction) { - LayoutCanvas canvas = mEditor.getCanvasControl(); - double s = canvas.getScale(); - - if (direction > 0) { - s = s * 1.2; - } else { - s = s / 1.2; - } - - // Some operations are faster if the zoom is EXACTLY 1.0 rather than ALMOST 1.0. - // (This is because there is a fast-path when image copying and the scale is 1.0; - // in that case it does not have to do any scaling). - // - // If you zoom out 10 times and then back in 10 times, small rounding errors mean - // that you end up with a scale=1.0000000000000004. In the cases, when you get close - // to 1.0, just make the zoom an exact 1.0. - if (Math.abs(s-1.0) < 0.0001) { - s = 1.0; - } - - canvas.setScale(s, true /*redraw*/); - } - - /** - * Reset the canvas scale to 100% - */ - void resetScale() { - mEditor.getCanvasControl().setScale(1, true /*redraw*/); - } - - /** - * Reset the canvas scale to best fit (so content is as large as possible without scrollbars) - */ - void rescaleToFit(boolean onlyZoomOut) { - mEditor.getCanvasControl().setFitScale(onlyZoomOut, true /*allowZoomIn*/); - } - - boolean rescaleToReal(boolean real) { - if (real) { - return computeAndSetRealScale(true /*redraw*/); - } else { - // reset the scale to 100% - mEditor.getCanvasControl().setScale(1, true /*redraw*/); - return true; - } - } - - boolean computeAndSetRealScale(boolean redraw) { - // compute average dpi of X and Y - ConfigurationChooser chooser = mEditor.getConfigurationChooser(); - Configuration config = chooser.getConfiguration(); - Device device = config.getDevice(); - Screen screen = device.getDefaultHardware().getScreen(); - double dpi = (screen.getXdpi() + screen.getYdpi()) / 2.; - - // get the monitor dpi - float monitor = AdtPrefs.getPrefs().getMonitorDensity(); - if (monitor == 0.f) { - ResolutionChooserDialog dialog = new ResolutionChooserDialog(chooser.getShell()); - if (dialog.open() == Window.OK) { - monitor = dialog.getDensity(); - AdtPrefs.getPrefs().setMonitorDensity(monitor); - } else { - return false; - } - } - - mEditor.getCanvasControl().setScale(monitor / dpi, redraw); - return true; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java deleted file mode 100644 index 814b82cec..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java +++ /dev/null @@ -1,1720 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.IDragElement.IDragAttribute; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Point; -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.common.rendering.api.RenderSession; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription; -import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; -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.uimodel.UiDocumentNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.lint.LintEditAction; -import com.android.resources.Density; - -import org.eclipse.core.filesystem.EFS; -import org.eclipse.core.filesystem.IFileStore; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IContributionItem; -import org.eclipse.jface.action.IMenuManager; -import org.eclipse.jface.action.IStatusLineManager; -import org.eclipse.jface.action.MenuManager; -import org.eclipse.jface.action.Separator; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.StyledText; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DragSource; -import org.eclipse.swt.dnd.DropTarget; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.events.ControlAdapter; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.MenuDetectEvent; -import org.eclipse.swt.events.MenuDetectListener; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.PaintEvent; -import org.eclipse.swt.events.PaintListener; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Canvas; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IActionBars; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IEditorSite; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.actions.ActionFactory; -import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction; -import org.eclipse.ui.actions.ContributionItemFactory; -import org.eclipse.ui.ide.IDE; -import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; -import org.eclipse.ui.texteditor.ITextEditor; -import org.w3c.dom.Node; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Displays the image rendered by the {@link GraphicalEditorPart} and handles - * the interaction with the widgets. - * <p/> - * {@link LayoutCanvas} implements the "Canvas" control. The editor part - * actually uses the {@link LayoutCanvasViewer}, which is a JFace viewer wrapper - * around this control. - * <p/> - * The LayoutCanvas contains the painting logic for the canvas. Selection, - * clipboard, view management etc. is handled in separate helper classes. - * - * @since GLE2 - */ -@SuppressWarnings("restriction") // For WorkBench "Show In" support -public class LayoutCanvas extends Canvas { - private final static QualifiedName NAME_ZOOM = - new QualifiedName(AdtPlugin.PLUGIN_ID, "zoom");//$NON-NLS-1$ - - private static final boolean DEBUG = false; - - static final String PREFIX_CANVAS_ACTION = "canvas_action_"; //$NON-NLS-1$ - - /** The layout editor that uses this layout canvas. */ - private final LayoutEditorDelegate mEditorDelegate; - - /** The Rules Engine, associated with the current project. */ - private RulesEngine mRulesEngine; - - /** GC wrapper given to the IViewRule methods. The GC itself is only defined in the - * context of {@link #onPaint(PaintEvent)}; otherwise it is null. */ - private GCWrapper mGCWrapper; - - /** Default font used on the canvas. Do not dispose, it's a system font. */ - private Font mFont; - - /** Current hover view info. Null when no mouse hover. */ - private CanvasViewInfo mHoverViewInfo; - - /** When true, always display the outline of all views. */ - private boolean mShowOutline; - - /** When true, display the outline of all empty parent views. */ - private boolean mShowInvisible; - - /** Drop target associated with this composite. */ - private DropTarget mDropTarget; - - /** Factory that can create {@link INode} proxies. */ - private final @NonNull NodeFactory mNodeFactory = new NodeFactory(this); - - /** Vertical scaling & scrollbar information. */ - private final CanvasTransform mVScale; - - /** Horizontal scaling & scrollbar information. */ - private final CanvasTransform mHScale; - - /** Drag source associated with this canvas. */ - private DragSource mDragSource; - - /** - * The current Outline Page, to set its model. - * It isn't possible to call OutlinePage2.dispose() in this.dispose(). - * this.dispose() is called from GraphicalEditorPart.dispose(), - * when page's widget is already disposed. - * Added the DisposeListener to OutlinePage2 in order to correctly dispose this page. - **/ - private OutlinePage mOutlinePage; - - /** Delete action for the Edit or context menu. */ - private Action mDeleteAction; - - /** Select-All action for the Edit or context menu. */ - private Action mSelectAllAction; - - /** Paste action for the Edit or context menu. */ - private Action mPasteAction; - - /** Cut action for the Edit or context menu. */ - private Action mCutAction; - - /** Copy action for the Edit or context menu. */ - private Action mCopyAction; - - /** Undo action: delegates to the text editor */ - private IAction mUndoAction; - - /** Redo action: delegates to the text editor */ - private IAction mRedoAction; - - /** Root of the context menu. */ - private MenuManager mMenuManager; - - /** The view hierarchy associated with this canvas. */ - private final ViewHierarchy mViewHierarchy = new ViewHierarchy(this); - - /** The selection in the canvas. */ - private final SelectionManager mSelectionManager = new SelectionManager(this); - - /** The overlay which paints the optional outline. */ - private OutlineOverlay mOutlineOverlay; - - /** The overlay which paints outlines around empty children */ - private EmptyViewsOverlay mEmptyOverlay; - - /** The overlay which paints the mouse hover. */ - private HoverOverlay mHoverOverlay; - - /** The overlay which paints the lint warnings */ - private LintOverlay mLintOverlay; - - /** The overlay which paints the selection. */ - private SelectionOverlay mSelectionOverlay; - - /** The overlay which paints the rendered layout image. */ - private ImageOverlay mImageOverlay; - - /** The overlay which paints masks hiding everything but included content. */ - private IncludeOverlay mIncludeOverlay; - - /** Configuration previews shown next to the layout */ - private final RenderPreviewManager mPreviewManager; - - /** - * Gesture Manager responsible for identifying mouse, keyboard and drag and - * drop events. - */ - private final GestureManager mGestureManager = new GestureManager(this); - - /** - * When set, performs a zoom-to-fit when the next rendering image arrives. - */ - private boolean mZoomFitNextImage; - - /** - * Native clipboard support. - */ - private ClipboardSupport mClipboardSupport; - - /** Tooltip manager for lint warnings */ - private LintTooltipManager mLintTooltipManager; - - private Color mBackgroundColor; - - /** - * Creates a new {@link LayoutCanvas} widget - * - * @param editorDelegate the associated editor delegate - * @param rulesEngine the rules engine - * @param parent parent SWT widget - * @param style the SWT style - */ - public LayoutCanvas(LayoutEditorDelegate editorDelegate, - RulesEngine rulesEngine, - Composite parent, - int style) { - super(parent, style | SWT.DOUBLE_BUFFERED | SWT.V_SCROLL | SWT.H_SCROLL); - mEditorDelegate = editorDelegate; - mRulesEngine = rulesEngine; - - mBackgroundColor = new Color(parent.getDisplay(), 150, 150, 150); - setBackground(mBackgroundColor); - - mClipboardSupport = new ClipboardSupport(this, parent); - mHScale = new CanvasTransform(this, getHorizontalBar()); - mVScale = new CanvasTransform(this, getVerticalBar()); - mPreviewManager = new RenderPreviewManager(this); - - // Unit test suite passes a null here; TODO: Replace with mocking - IFile file = editorDelegate != null ? editorDelegate.getEditor().getInputFile() : null; - if (file != null) { - String zoom = AdtPlugin.getFileProperty(file, NAME_ZOOM); - if (zoom != null) { - try { - double initialScale = Double.parseDouble(zoom); - if (initialScale > 0.1) { - mHScale.setScale(initialScale); - mVScale.setScale(initialScale); - } - } catch (NumberFormatException nfe) { - // Ignore - use zoom=100% - } - } else { - mZoomFitNextImage = true; - } - } - - mGCWrapper = new GCWrapper(mHScale, mVScale); - - Display display = getDisplay(); - mFont = display.getSystemFont(); - - // --- Set up graphic overlays - // mOutlineOverlay and mEmptyOverlay are initialized lazily - mHoverOverlay = new HoverOverlay(this, mHScale, mVScale); - mHoverOverlay.create(display); - mSelectionOverlay = new SelectionOverlay(this); - mSelectionOverlay.create(display); - mImageOverlay = new ImageOverlay(this, mHScale, mVScale); - mIncludeOverlay = new IncludeOverlay(this); - mImageOverlay.create(display); - mLintOverlay = new LintOverlay(this); - mLintOverlay.create(display); - - // --- Set up listeners - addPaintListener(new PaintListener() { - @Override - public void paintControl(PaintEvent e) { - onPaint(e); - } - }); - - addControlListener(new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - super.controlResized(e); - - // Check editor state: - LayoutWindowCoordinator coordinator = null; - IEditorSite editorSite = getEditorDelegate().getEditor().getEditorSite(); - IWorkbenchWindow window = editorSite.getWorkbenchWindow(); - if (window != null) { - coordinator = LayoutWindowCoordinator.get(window, false); - if (coordinator != null) { - coordinator.syncMaximizedState(editorSite.getPage()); - } - } - - updateScrollBars(); - - // Update the zoom level in the canvas when you toggle the zoom - if (coordinator != null) { - mZoomCheck.run(); - } else { - // During startup, delay updates which can trigger further layout - getDisplay().asyncExec(mZoomCheck); - - } - } - }); - - // --- setup drag'n'drop --- - // DND Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html - - mDropTarget = createDropTarget(this); - mDragSource = createDragSource(this); - mGestureManager.registerListeners(mDragSource, mDropTarget); - - if (mEditorDelegate == null) { - // TODO: In another CL we should use EasyMock/objgen to provide an editor. - return; // Unit test - } - - // --- setup context menu --- - setupGlobalActionHandlers(); - createContextMenu(); - - // --- setup outline --- - // Get the outline associated with this editor, if any and of the right type. - if (editorDelegate != null) { - mOutlinePage = editorDelegate.getGraphicalOutline(); - } - - mLintTooltipManager = new LintTooltipManager(this); - mLintTooltipManager.register(); - } - - void updateScrollBars() { - Rectangle clientArea = getClientArea(); - Image image = mImageOverlay.getImage(); - if (image != null) { - ImageData imageData = image.getImageData(); - int clientWidth = clientArea.width; - int clientHeight = clientArea.height; - - int imageWidth = imageData.width; - int imageHeight = imageData.height; - - int fullWidth = imageWidth; - int fullHeight = imageHeight; - - if (mPreviewManager.hasPreviews()) { - fullHeight = Math.max(fullHeight, - (int) (mPreviewManager.getHeight() / mHScale.getScale())); - } - - if (clientWidth == 0) { - clientWidth = imageWidth; - Shell shell = getShell(); - if (shell != null) { - org.eclipse.swt.graphics.Point size = shell.getSize(); - if (size.x > 0) { - clientWidth = size.x * 70 / 100; - } - } - } - if (clientHeight == 0) { - clientHeight = imageHeight; - Shell shell = getShell(); - if (shell != null) { - org.eclipse.swt.graphics.Point size = shell.getSize(); - if (size.y > 0) { - clientWidth = size.y * 80 / 100; - } - } - } - - mHScale.setSize(imageWidth, fullWidth, clientWidth); - mVScale.setSize(imageHeight, fullHeight, clientHeight); - } - } - - private Runnable mZoomCheck = new Runnable() { - private Boolean mWasZoomed; - - @Override - public void run() { - if (isDisposed()) { - return; - } - - IEditorSite editorSite = getEditorDelegate().getEditor().getEditorSite(); - IWorkbenchWindow window = editorSite.getWorkbenchWindow(); - if (window != null) { - LayoutWindowCoordinator coordinator = LayoutWindowCoordinator.get(window, false); - if (coordinator != null) { - Boolean zoomed = coordinator.isEditorMaximized(); - if (mWasZoomed != zoomed) { - if (mWasZoomed != null) { - LayoutActionBar actionBar = getGraphicalEditor().getLayoutActionBar(); - if (actionBar.isZoomingAllowed()) { - setFitScale(true /*onlyZoomOut*/, true /*allowZoomIn*/); - } - } - mWasZoomed = zoomed; - } - } - } - } - }; - - void handleKeyPressed(KeyEvent e) { - // Set up backspace as an alias for the delete action within the canvas. - // On most Macs there is no delete key - though there IS a key labeled - // "Delete" and it sends a backspace key code! In short, for Macs we should - // treat backspace as delete, and it's harmless (and probably useful) to - // handle backspace for other platforms as well. - if (e.keyCode == SWT.BS) { - mDeleteAction.run(); - } else if (e.keyCode == SWT.ESC) { - mSelectionManager.selectParent(); - } else if (e.keyCode == DynamicContextMenu.DEFAULT_ACTION_KEY) { - mSelectionManager.performDefaultAction(); - } else if (e.keyCode == 'r') { - // Keep key bindings in sync with {@link DynamicContextMenu#createPlainAction} - // TODO: Find a way to look up the Eclipse key bindings and attempt - // to use the current keymap's rename action. - if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) { - // Command+Option+R - if ((e.stateMask & (SWT.MOD1 | SWT.MOD3)) == (SWT.MOD1 | SWT.MOD3)) { - mSelectionManager.performRename(); - } - } else { - // Alt+Shift+R - if ((e.stateMask & (SWT.MOD2 | SWT.MOD3)) == (SWT.MOD2 | SWT.MOD3)) { - mSelectionManager.performRename(); - } - } - } else { - // Zooming actions - char c = e.character; - LayoutActionBar actionBar = getGraphicalEditor().getLayoutActionBar(); - if (c == '1' && actionBar.isZoomingAllowed()) { - setScale(1, true); - } else if (c == '0' && actionBar.isZoomingAllowed()) { - setFitScale(true, true /*allowZoomIn*/); - } else if (e.keyCode == '0' && (e.stateMask & SWT.MOD2) != 0 - && actionBar.isZoomingAllowed()) { - setFitScale(false, true /*allowZoomIn*/); - } else if ((c == '+' || c == '=') && actionBar.isZoomingAllowed()) { - if ((e.stateMask & SWT.MOD1) != 0) { - mPreviewManager.zoomIn(); - } else { - actionBar.rescale(1); - } - } else if (c == '-' && actionBar.isZoomingAllowed()) { - if ((e.stateMask & SWT.MOD1) != 0) { - mPreviewManager.zoomOut(); - } else { - actionBar.rescale(-1); - } - } - } - } - - @Override - public void dispose() { - super.dispose(); - - mGestureManager.unregisterListeners(mDragSource, mDropTarget); - - if (mLintTooltipManager != null) { - mLintTooltipManager.unregister(); - mLintTooltipManager = null; - } - - if (mDropTarget != null) { - mDropTarget.dispose(); - mDropTarget = null; - } - - if (mRulesEngine != null) { - mRulesEngine.dispose(); - mRulesEngine = null; - } - - if (mDragSource != null) { - mDragSource.dispose(); - mDragSource = null; - } - - if (mClipboardSupport != null) { - mClipboardSupport.dispose(); - mClipboardSupport = null; - } - - if (mGCWrapper != null) { - mGCWrapper.dispose(); - mGCWrapper = null; - } - - if (mOutlineOverlay != null) { - mOutlineOverlay.dispose(); - mOutlineOverlay = null; - } - - if (mEmptyOverlay != null) { - mEmptyOverlay.dispose(); - mEmptyOverlay = null; - } - - if (mHoverOverlay != null) { - mHoverOverlay.dispose(); - mHoverOverlay = null; - } - - if (mSelectionOverlay != null) { - mSelectionOverlay.dispose(); - mSelectionOverlay = null; - } - - if (mImageOverlay != null) { - mImageOverlay.dispose(); - mImageOverlay = null; - } - - if (mIncludeOverlay != null) { - mIncludeOverlay.dispose(); - mIncludeOverlay = null; - } - - if (mLintOverlay != null) { - mLintOverlay.dispose(); - mLintOverlay = null; - } - - if (mBackgroundColor != null) { - mBackgroundColor.dispose(); - mBackgroundColor = null; - } - - mPreviewManager.disposePreviews(); - mViewHierarchy.dispose(); - } - - /** - * Returns the configuration preview manager for this canvas - * - * @return the configuration preview manager for this canvas - */ - @NonNull - public RenderPreviewManager getPreviewManager() { - return mPreviewManager; - } - - /** Returns the Rules Engine, associated with the current project. */ - RulesEngine getRulesEngine() { - return mRulesEngine; - } - - /** Sets the Rules Engine, associated with the current project. */ - void setRulesEngine(RulesEngine rulesEngine) { - mRulesEngine = rulesEngine; - } - - /** - * Returns the factory to use to convert from {@link CanvasViewInfo} or from - * {@link UiViewElementNode} to {@link INode} proxies. - * - * @return the node factory - */ - @NonNull - public NodeFactory getNodeFactory() { - return mNodeFactory; - } - - /** - * Returns the GCWrapper used to paint view rules. - * - * @return The GCWrapper used to paint view rules - */ - GCWrapper getGcWrapper() { - return mGCWrapper; - } - - /** - * Returns the {@link LayoutEditorDelegate} associated with this canvas. - * - * @return the delegate - */ - public LayoutEditorDelegate getEditorDelegate() { - return mEditorDelegate; - } - - /** - * Returns the current {@link ImageOverlay} painting the rendered result - * - * @return the image overlay responsible for painting the rendered result, never null - */ - ImageOverlay getImageOverlay() { - return mImageOverlay; - } - - /** - * Returns the current {@link SelectionOverlay} painting the selection highlights - * - * @return the selection overlay responsible for painting the selection highlights, - * never null - */ - SelectionOverlay getSelectionOverlay() { - return mSelectionOverlay; - } - - /** - * Returns the {@link GestureManager} associated with this canvas. - * - * @return the {@link GestureManager} associated with this canvas, never null. - */ - GestureManager getGestureManager() { - return mGestureManager; - } - - /** - * Returns the current {@link HoverOverlay} painting the mouse hover. - * - * @return the hover overlay responsible for painting the mouse hover, - * never null - */ - HoverOverlay getHoverOverlay() { - return mHoverOverlay; - } - - /** - * Returns the horizontal {@link CanvasTransform} transform object, which can map - * a layout point into a control point. - * - * @return A {@link CanvasTransform} for mapping between layout and control - * coordinates in the horizontal dimension. - */ - CanvasTransform getHorizontalTransform() { - return mHScale; - } - - /** - * Returns the vertical {@link CanvasTransform} transform object, which can map a - * layout point into a control point. - * - * @return A {@link CanvasTransform} for mapping between layout and control - * coordinates in the vertical dimension. - */ - CanvasTransform getVerticalTransform() { - return mVScale; - } - - /** - * Returns the {@link OutlinePage} associated with this canvas - * - * @return the {@link OutlinePage} associated with this canvas - */ - public OutlinePage getOutlinePage() { - return mOutlinePage; - } - - /** - * Returns the {@link SelectionManager} associated with this canvas. - * - * @return The {@link SelectionManager} holding the selection for this - * canvas. Never null. - */ - public SelectionManager getSelectionManager() { - return mSelectionManager; - } - - /** - * Returns the {@link ViewHierarchy} object associated with this canvas, - * holding the most recent rendered view of the scene, if valid. - * - * @return The {@link ViewHierarchy} object associated with this canvas. - * Never null. - */ - public ViewHierarchy getViewHierarchy() { - return mViewHierarchy; - } - - /** - * Returns the {@link ClipboardSupport} object associated with this canvas. - * - * @return The {@link ClipboardSupport} object for this canvas. Null only after dispose. - */ - public ClipboardSupport getClipboardSupport() { - return mClipboardSupport; - } - - /** Returns the Select All action bound to this canvas */ - Action getSelectAllAction() { - return mSelectAllAction; - } - - /** Returns the associated {@link GraphicalEditorPart} */ - GraphicalEditorPart getGraphicalEditor() { - return mEditorDelegate.getGraphicalEditor(); - } - - /** - * Sets the result of the layout rendering. The result object indicates if the layout - * rendering succeeded. If it did, it contains a bitmap and the objects rectangles. - * - * Implementation detail: the bridge's computeLayout() method already returns a newly - * allocated ILayourResult. That means we can keep this result and hold on to it - * when it is valid. - * - * @param session The new scene, either valid or not. - * @param explodedNodes The set of individual nodes the layout computer was asked to - * explode. Note that these are independent of the explode-all mode where - * all views are exploded; this is used only for the mode ( - * {@link #showInvisibleViews(boolean)}) where individual invisible nodes - * are padded during certain interactions. - */ - void setSession(RenderSession session, Set<UiElementNode> explodedNodes, - boolean layoutlib5) { - // disable any hover - clearHover(); - - mViewHierarchy.setSession(session, explodedNodes, layoutlib5); - if (mViewHierarchy.isValid() && session != null) { - Image image = mImageOverlay.setImage(session.getImage(), - session.isAlphaChannelImage()); - - mOutlinePage.setModel(mViewHierarchy.getRoot()); - getGraphicalEditor().setModel(mViewHierarchy.getRoot()); - - if (image != null) { - updateScrollBars(); - if (mZoomFitNextImage) { - // Must be run asynchronously because getClientArea() returns 0 bounds - // when the editor is being initialized - getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - if (!isDisposed()) { - ensureZoomed(); - } - } - }); - } - - // Ensure that if we have a a preview mode enabled, it's shown - syncPreviewMode(); - } - } - - redraw(); - } - - void ensureZoomed() { - if (mZoomFitNextImage && getClientArea().height > 0) { - mZoomFitNextImage = false; - LayoutActionBar actionBar = getGraphicalEditor().getLayoutActionBar(); - if (actionBar.isZoomingAllowed()) { - setFitScale(true, true /*allowZoomIn*/); - } - } - } - - void setShowOutline(boolean newState) { - mShowOutline = newState; - redraw(); - } - - /** - * Returns the zoom scale factor of the canvas (the amount the full - * resolution render of the device is zoomed before being shown on the - * canvas) - * - * @return the image scale - */ - public double getScale() { - return mHScale.getScale(); - } - - void setScale(double scale, boolean redraw) { - if (scale <= 0.0) { - scale = 1.0; - } - - if (scale == getScale()) { - return; - } - - mHScale.setScale(scale); - mVScale.setScale(scale); - if (redraw) { - redraw(); - } - - // Clear the zoom setting if it is almost identical to 1.0 - String zoomValue = (Math.abs(scale - 1.0) < 0.0001) ? null : Double.toString(scale); - IFile file = mEditorDelegate.getEditor().getInputFile(); - if (file != null) { - AdtPlugin.setFileProperty(file, NAME_ZOOM, zoomValue); - } - } - - /** - * Scales the canvas to best fit - * - * @param onlyZoomOut if true, then the zooming factor will never be larger than 1, - * which means that this function will zoom out if necessary to show the - * rendered image, but it will never zoom in. - * TODO: Rename this, it sounds like it conflicts with allowZoomIn, - * even though one is referring to the zoom level and one is referring - * to the overall act of scaling above/below 1. - * @param allowZoomIn if false, then if the computed zoom factor is smaller than - * the current zoom factor, it will be ignored. - */ - public void setFitScale(boolean onlyZoomOut, boolean allowZoomIn) { - ImageOverlay imageOverlay = getImageOverlay(); - if (imageOverlay == null) { - return; - } - Image image = imageOverlay.getImage(); - if (image != null) { - Rectangle canvasSize = getClientArea(); - int canvasWidth = canvasSize.width; - int canvasHeight = canvasSize.height; - - boolean hasPreviews = mPreviewManager.hasPreviews(); - if (hasPreviews) { - canvasWidth = 2 * canvasWidth / 3; - } else { - canvasWidth -= 4; - canvasHeight -= 4; - } - - ImageData imageData = image.getImageData(); - int sceneWidth = imageData.width; - int sceneHeight = imageData.height; - if (sceneWidth == 0.0 || sceneHeight == 0.0) { - return; - } - - if (imageOverlay.getShowDropShadow()) { - sceneWidth += 2 * ImageUtils.SHADOW_SIZE; - sceneHeight += 2 * ImageUtils.SHADOW_SIZE; - } - - // Reduce the margins if necessary - int hDelta = canvasWidth - sceneWidth; - int hMargin = 0; - if (hDelta > 2 * CanvasTransform.DEFAULT_MARGIN) { - hMargin = CanvasTransform.DEFAULT_MARGIN; - } else if (hDelta > 0) { - hMargin = hDelta / 2; - } - - int vDelta = canvasHeight - sceneHeight; - int vMargin = 0; - if (vDelta > 2 * CanvasTransform.DEFAULT_MARGIN) { - vMargin = CanvasTransform.DEFAULT_MARGIN; - } else if (vDelta > 0) { - vMargin = vDelta / 2; - } - - double hScale = (canvasWidth - 2 * hMargin) / (double) sceneWidth; - double vScale = (canvasHeight - 2 * vMargin) / (double) sceneHeight; - - double scale = Math.min(hScale, vScale); - - if (onlyZoomOut) { - scale = Math.min(1.0, scale); - } - - if (!allowZoomIn && scale > getScale()) { - return; - } - - setScale(scale, true); - } - } - - /** - * Transforms a point, expressed in layout coordinates, into "client" coordinates - * relative to the control (and not relative to the display). - * - * @param canvasX X in the canvas coordinates - * @param canvasY Y in the canvas coordinates - * @return A new {@link Point} in control client coordinates (not display coordinates) - */ - Point layoutToControlPoint(int canvasX, int canvasY) { - int x = mHScale.translate(canvasX); - int y = mVScale.translate(canvasY); - return new Point(x, y); - } - - /** - * Returns the action for the context menu corresponding to the given action id. - * <p/> - * For global actions such as copy or paste, the action id must be composed of - * the {@link #PREFIX_CANVAS_ACTION} followed by one of {@link ActionFactory}'s - * action ids. - * <p/> - * Returns null if there's no action for the given id. - */ - IAction getAction(String actionId) { - String prefix = PREFIX_CANVAS_ACTION; - if (mMenuManager == null || - actionId == null || - !actionId.startsWith(prefix)) { - return null; - } - - actionId = actionId.substring(prefix.length()); - - for (IContributionItem contrib : mMenuManager.getItems()) { - if (contrib instanceof ActionContributionItem && - actionId.equals(contrib.getId())) { - return ((ActionContributionItem) contrib).getAction(); - } - } - - return null; - } - - //--------------- - - /** - * Paints the canvas in response to paint events. - */ - private void onPaint(PaintEvent e) { - GC gc = e.gc; - gc.setFont(mFont); - mGCWrapper.setGC(gc); - try { - if (!mImageOverlay.isHiding()) { - mImageOverlay.paint(gc); - } - - mPreviewManager.paint(gc); - - if (mShowOutline) { - if (mOutlineOverlay == null) { - mOutlineOverlay = new OutlineOverlay(mViewHierarchy, mHScale, mVScale); - mOutlineOverlay.create(getDisplay()); - } - if (!mOutlineOverlay.isHiding()) { - mOutlineOverlay.paint(gc); - } - } - - if (mShowInvisible) { - if (mEmptyOverlay == null) { - mEmptyOverlay = new EmptyViewsOverlay(mViewHierarchy, mHScale, mVScale); - mEmptyOverlay.create(getDisplay()); - } - if (!mEmptyOverlay.isHiding()) { - mEmptyOverlay.paint(gc); - } - } - - if (!mHoverOverlay.isHiding()) { - mHoverOverlay.paint(gc); - } - - if (!mLintOverlay.isHiding()) { - mLintOverlay.paint(gc); - } - - if (!mIncludeOverlay.isHiding()) { - mIncludeOverlay.paint(gc); - } - - if (!mSelectionOverlay.isHiding()) { - mSelectionOverlay.paint(mSelectionManager, mGCWrapper, gc, mRulesEngine); - } - mGestureManager.paint(gc); - - } finally { - mGCWrapper.setGC(null); - } - } - - /** - * Shows or hides invisible parent views, which are views which have empty bounds and - * no children. The nodes which will be shown are provided by - * {@link #getNodesToExplode()}. - * - * @param show When true, any invisible parent nodes are padded and highlighted - * ("exploded"), and when false any formerly exploded nodes are hidden. - */ - void showInvisibleViews(boolean show) { - if (mShowInvisible == show) { - return; - } - mShowInvisible = show; - - // Optimization: Avoid doing work when we don't have invisible parents (on show) - // or formerly exploded nodes (on hide). - if (show && !mViewHierarchy.hasInvisibleParents()) { - return; - } else if (!show && !mViewHierarchy.hasExplodedParents()) { - return; - } - - mEditorDelegate.recomputeLayout(); - } - - /** - * Returns a set of nodes that should be exploded (forced non-zero padding during render), - * or null if no nodes should be exploded. (Note that this is independent of the - * explode-all mode, where all nodes are padded -- that facility does not use this - * mechanism, which is only intended to be used to expose invisible parent nodes. - * - * @return The set of invisible parents, or null if no views should be expanded. - */ - public Set<UiElementNode> getNodesToExplode() { - if (mShowInvisible) { - return mViewHierarchy.getInvisibleNodes(); - } - - // IF we have selection, and IF we have invisible nodes in the view, - // see if any of the selected items are among the invisible nodes, and if so - // add them to a lazily constructed set which we pass back for rendering. - Set<UiElementNode> result = null; - List<SelectionItem> selections = mSelectionManager.getSelections(); - if (selections.size() > 0) { - List<CanvasViewInfo> invisibleParents = mViewHierarchy.getInvisibleViews(); - if (invisibleParents.size() > 0) { - for (SelectionItem item : selections) { - CanvasViewInfo viewInfo = item.getViewInfo(); - // O(n^2) here, but both the selection size and especially the - // invisibleParents size are expected to be small - if (invisibleParents.contains(viewInfo)) { - UiViewElementNode node = viewInfo.getUiViewNode(); - if (node != null) { - if (result == null) { - result = new HashSet<UiElementNode>(); - } - result.add(node); - } - } - } - } - } - - return result; - } - - /** - * Clears the hover. - */ - void clearHover() { - mHoverOverlay.clearHover(); - } - - /** - * Hover on top of a known child. - */ - void hover(MouseEvent e) { - // Check if a button is pressed; no hovers during drags - if ((e.stateMask & SWT.BUTTON_MASK) != 0) { - clearHover(); - return; - } - - LayoutPoint p = ControlPoint.create(this, e).toLayout(); - CanvasViewInfo vi = mViewHierarchy.findViewInfoAt(p); - - // We don't hover on the root since it's not a widget per see and it is always there. - // We also skip spacers... - if (vi != null && (vi.isRoot() || vi.isHidden())) { - vi = null; - } - - boolean needsUpdate = vi != mHoverViewInfo; - mHoverViewInfo = vi; - - if (vi == null) { - clearHover(); - } else { - Rectangle r = vi.getSelectionRect(); - mHoverOverlay.setHover(r.x, r.y, r.width, r.height); - } - - if (needsUpdate) { - redraw(); - } - } - - /** - * Shows the given {@link CanvasViewInfo}, which can mean exposing its XML or if it's - * an included element, its corresponding file. - * - * @param vi the {@link CanvasViewInfo} to be shown - */ - public void show(CanvasViewInfo vi) { - String url = vi.getIncludeUrl(); - if (url != null) { - showInclude(url); - } else { - showXml(vi); - } - } - - /** - * Shows the layout file referenced by the given url in the same project. - * - * @param url The layout attribute url of the form @layout/foo - */ - private void showInclude(String url) { - GraphicalEditorPart graphicalEditor = getGraphicalEditor(); - IPath filePath = graphicalEditor.findResourceFile(url); - if (filePath == null) { - // Should not be possible - if the URL had been bad, then we wouldn't - // have been able to render the scene and you wouldn't have been able - // to click on it - return; - } - - // Save the including file, if necessary: without it, the "Show Included In" - // facility which is invoked automatically will not work properly if the <include> - // tag is not in the saved version of the file, since the outer file is read from - // disk rather than from memory. - IEditorSite editorSite = graphicalEditor.getEditorSite(); - IWorkbenchPage page = editorSite.getPage(); - page.saveEditor(mEditorDelegate.getEditor(), false); - - IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); - IFile xmlFile = null; - IPath workspacePath = workspace.getLocation(); - if (workspacePath.isPrefixOf(filePath)) { - IPath relativePath = filePath.makeRelativeTo(workspacePath); - xmlFile = (IFile) workspace.findMember(relativePath); - } else if (filePath.isAbsolute()) { - xmlFile = workspace.getFileForLocation(filePath); - } - if (xmlFile != null) { - IFile leavingFile = graphicalEditor.getEditedFile(); - Reference next = Reference.create(graphicalEditor.getEditedFile()); - - try { - IEditorPart openAlready = EditorUtility.isOpenInEditor(xmlFile); - - // Show the included file as included within this click source? - if (openAlready != null) { - LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(openAlready); - if (delegate != null) { - GraphicalEditorPart gEditor = delegate.getGraphicalEditor(); - if (gEditor != null && - gEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) { - gEditor.showIn(next); - } - } - } else { - try { - // Set initial state of a new file - // TODO: Only set rendering target portion of the state - String state = ConfigurationDescription.getDescription(leavingFile); - xmlFile.setSessionProperty(GraphicalEditorPart.NAME_INITIAL_STATE, - state); - } catch (CoreException e) { - // pass - } - - if (graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) { - try { - xmlFile.setSessionProperty(GraphicalEditorPart.NAME_INCLUDE, next); - } catch (CoreException e) { - // pass - worst that can happen is that we don't - //start with inclusion - } - } - } - - EditorUtility.openInEditor(xmlFile, true); - return; - } catch (PartInitException ex) { - AdtPlugin.log(ex, "Can't open %$1s", url); //$NON-NLS-1$ - } - } else { - // It's not a path in the workspace; look externally - // (this is probably an @android: path) - if (filePath.isAbsolute()) { - IFileStore fileStore = EFS.getLocalFileSystem().getStore(filePath); - // fileStore = fileStore.getChild(names[i]); - if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) { - try { - IDE.openEditorOnFileStore(page, fileStore); - return; - } catch (PartInitException ex) { - AdtPlugin.log(ex, "Can't open %$1s", url); //$NON-NLS-1$ - } - } - } - } - - // Failed: display message to the user - String message = String.format("Could not find resource %1$s", url); - IStatusLineManager status = editorSite.getActionBars().getStatusLineManager(); - status.setErrorMessage(message); - getDisplay().beep(); - } - - /** - * Returns the layout resource name of this layout - * - * @return the layout resource name of this layout - */ - public String getLayoutResourceName() { - GraphicalEditorPart graphicalEditor = getGraphicalEditor(); - return graphicalEditor.getLayoutResourceName(); - } - - /** - * Returns the layout resource url of the current layout - * - * @return - */ - /* - public String getMe() { - GraphicalEditorPart graphicalEditor = getGraphicalEditor(); - IFile editedFile = graphicalEditor.getEditedFile(); - return editedFile.getProjectRelativePath().toOSString(); - } - */ - - /** - * Show the XML element corresponding to the given {@link CanvasViewInfo} (unless it's - * a root). - * - * @param vi The clicked {@link CanvasViewInfo} whose underlying XML element we want - * to view - */ - private void showXml(CanvasViewInfo vi) { - // Warp to the text editor and show the corresponding XML for the - // double-clicked widget - if (vi.isRoot()) { - return; - } - - Node xmlNode = vi.getXmlNode(); - if (xmlNode != null) { - boolean found = mEditorDelegate.getEditor().show(xmlNode); - if (!found) { - getDisplay().beep(); - } - } - } - - //--------------- - - /** - * Helper to create the drag source for the given control. - * <p/> - * This is static with package-access so that {@link OutlinePage} can also - * create an exact copy of the source with the same attributes. - */ - /* package */static DragSource createDragSource(Control control) { - DragSource source = new DragSource(control, DND.DROP_COPY | DND.DROP_MOVE); - source.setTransfer(new Transfer[] { - TextTransfer.getInstance(), - SimpleXmlTransfer.getInstance() - }); - return source; - } - - /** - * Helper to create the drop target for the given control. - */ - private static DropTarget createDropTarget(Control control) { - DropTarget dropTarget = new DropTarget( - control, DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_DEFAULT); - dropTarget.setTransfer(new Transfer[] { - SimpleXmlTransfer.getInstance() - }); - return dropTarget; - } - - //--------------- - - /** - * Invoked by the constructor to add our cut/copy/paste/delete/select-all - * handlers in the global action handlers of this editor's site. - * <p/> - * This will enable the menu items under the global Edit menu and make them - * invoke our actions as needed. As a benefit, the corresponding shortcut - * accelerators will do what one would expect. - */ - private void setupGlobalActionHandlers() { - mCutAction = new Action() { - @Override - public void run() { - mClipboardSupport.cutSelectionToClipboard(mSelectionManager.getSnapshot()); - updateMenuActionState(); - } - }; - - copyActionAttributes(mCutAction, ActionFactory.CUT); - - mCopyAction = new Action() { - @Override - public void run() { - mClipboardSupport.copySelectionToClipboard(mSelectionManager.getSnapshot()); - updateMenuActionState(); - } - }; - - copyActionAttributes(mCopyAction, ActionFactory.COPY); - - mPasteAction = new Action() { - @Override - public void run() { - mClipboardSupport.pasteSelection(mSelectionManager.getSnapshot()); - updateMenuActionState(); - } - }; - - copyActionAttributes(mPasteAction, ActionFactory.PASTE); - - mDeleteAction = new Action() { - @Override - public void run() { - mClipboardSupport.deleteSelection( - getDeleteLabel(), - mSelectionManager.getSnapshot()); - } - }; - - copyActionAttributes(mDeleteAction, ActionFactory.DELETE); - - mSelectAllAction = new Action() { - @Override - public void run() { - GraphicalEditorPart graphicalEditor = getEditorDelegate().getGraphicalEditor(); - StyledText errorLabel = graphicalEditor.getErrorLabel(); - if (errorLabel.isFocusControl()) { - errorLabel.selectAll(); - return; - } - - mSelectionManager.selectAll(); - } - }; - - copyActionAttributes(mSelectAllAction, ActionFactory.SELECT_ALL); - } - - String getCutLabel() { - return mCutAction.getText(); - } - - String getDeleteLabel() { - // verb "Delete" from the DELETE action's title - return mDeleteAction.getText(); - } - - /** - * Updates menu actions that depends on the selection. - */ - void updateMenuActionState() { - List<SelectionItem> selections = getSelectionManager().getSelections(); - boolean hasSelection = !selections.isEmpty(); - if (hasSelection && selections.size() == 1 && selections.get(0).isRoot()) { - hasSelection = false; - } - - StyledText errorLabel = getGraphicalEditor().getErrorLabel(); - mCutAction.setEnabled(hasSelection); - mCopyAction.setEnabled(hasSelection || errorLabel.getSelectionCount() > 0); - mDeleteAction.setEnabled(hasSelection); - // Select All should *always* be selectable, regardless of whether anything - // is currently selected. - mSelectAllAction.setEnabled(true); - - // The paste operation is only available if we can paste our custom type. - // We do not currently support pasting random text (e.g. XML). Maybe later. - boolean hasSxt = mClipboardSupport.hasSxtOnClipboard(); - mPasteAction.setEnabled(hasSxt); - } - - /** - * Update the actions when this editor is activated - * - * @param bars the action bar for this canvas - */ - public void updateGlobalActions(@NonNull IActionBars bars) { - updateMenuActionState(); - - ITextEditor editor = mEditorDelegate.getEditor().getStructuredTextEditor(); - boolean graphical = getEditorDelegate().getEditor().getActivePage() == 0; - if (graphical) { - bars.setGlobalActionHandler(ActionFactory.CUT.getId(), mCutAction); - bars.setGlobalActionHandler(ActionFactory.COPY.getId(), mCopyAction); - bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), mPasteAction); - bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), mDeleteAction); - bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), mSelectAllAction); - - // Delegate the Undo and Redo actions to the text editor ones, but wrap them - // such that we run lint to update the results on the current page (this is - // normally done on each editor operation that goes through - // {@link AndroidXmlEditor#wrapUndoEditXmlModel}, but not undo/redo) - if (mUndoAction == null) { - IAction undoAction = editor.getAction(ActionFactory.UNDO.getId()); - mUndoAction = new LintEditAction(undoAction, getEditorDelegate().getEditor()); - } - bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), mUndoAction); - if (mRedoAction == null) { - IAction redoAction = editor.getAction(ActionFactory.REDO.getId()); - mRedoAction = new LintEditAction(redoAction, getEditorDelegate().getEditor()); - } - bars.setGlobalActionHandler(ActionFactory.REDO.getId(), mRedoAction); - } else { - bars.setGlobalActionHandler(ActionFactory.CUT.getId(), - editor.getAction(ActionFactory.CUT.getId())); - bars.setGlobalActionHandler(ActionFactory.COPY.getId(), - editor.getAction(ActionFactory.COPY.getId())); - bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), - editor.getAction(ActionFactory.PASTE.getId())); - bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), - editor.getAction(ActionFactory.DELETE.getId())); - bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), - editor.getAction(ActionFactory.SELECT_ALL.getId())); - bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), - editor.getAction(ActionFactory.UNDO.getId())); - bars.setGlobalActionHandler(ActionFactory.REDO.getId(), - editor.getAction(ActionFactory.REDO.getId())); - } - - bars.updateActionBars(); - } - - /** - * Helper for {@link #setupGlobalActionHandlers()}. - * Copies the action attributes form the given {@link ActionFactory}'s action to - * our action. - * <p/> - * {@link ActionFactory} provides access to the standard global actions in Eclipse. - * <p/> - * This allows us to grab the standard labels and icons for the - * global actions such as copy, cut, paste, delete and select-all. - */ - private void copyActionAttributes(Action action, ActionFactory factory) { - IWorkbenchAction wa = factory.create( - mEditorDelegate.getEditor().getEditorSite().getWorkbenchWindow()); - action.setId(wa.getId()); - action.setText(wa.getText()); - action.setEnabled(wa.isEnabled()); - action.setDescription(wa.getDescription()); - action.setToolTipText(wa.getToolTipText()); - action.setAccelerator(wa.getAccelerator()); - action.setActionDefinitionId(wa.getActionDefinitionId()); - action.setImageDescriptor(wa.getImageDescriptor()); - action.setHoverImageDescriptor(wa.getHoverImageDescriptor()); - action.setDisabledImageDescriptor(wa.getDisabledImageDescriptor()); - action.setHelpListener(wa.getHelpListener()); - } - - /** - * Creates the context menu for the canvas. This is called once from the canvas' constructor. - * <p/> - * The menu has a static part with actions that are always available such as - * copy, cut, paste and show in > explorer. This is created by - * {@link #setupStaticMenuActions(IMenuManager)}. - * <p/> - * There's also a dynamic part that is populated by the rules of the - * selected elements, created by {@link DynamicContextMenu}. - */ - @SuppressWarnings("unused") - private void createContextMenu() { - - // This manager is the root of the context menu. - mMenuManager = new MenuManager() { - @Override - public boolean isDynamic() { - return true; - } - }; - - // Fill the menu manager with the static & dynamic actions - setupStaticMenuActions(mMenuManager); - new DynamicContextMenu(mEditorDelegate, this, mMenuManager); - Menu menu = mMenuManager.createContextMenu(this); - setMenu(menu); - - // Add listener to detect when the menu is about to be posted, such that - // we can sync the selection. Without this, you can right click on something - // in the canvas which is NOT selected, and the context menu will show items related - // to the selection, NOT the item you clicked on!! - addMenuDetectListener(new MenuDetectListener() { - @Override - public void menuDetected(MenuDetectEvent e) { - mSelectionManager.menuClick(e); - } - }); - } - - /** - * Invoked by {@link #createContextMenu()} to create our *static* context menu once. - * <p/> - * The content of the menu itself does not change. However the state of the - * various items is controlled by their associated actions. - * <p/> - * For cut/copy/paste/delete/select-all, we explicitly reuse the actions - * created by {@link #setupGlobalActionHandlers()}, so this method must be - * invoked after that one. - */ - private void setupStaticMenuActions(IMenuManager manager) { - manager.removeAll(); - - manager.add(new SelectionManager.SelectionMenu(getGraphicalEditor())); - manager.add(new Separator()); - manager.add(mCutAction); - manager.add(mCopyAction); - manager.add(mPasteAction); - manager.add(new Separator()); - manager.add(mDeleteAction); - manager.add(new Separator()); - manager.add(new PlayAnimationMenu(this)); - manager.add(new ExportScreenshotAction(this)); - manager.add(new Separator()); - - // Group "Show Included In" and "Show In" together - manager.add(new ShowWithinMenu(mEditorDelegate)); - - // Create a "Show In" sub-menu and automatically populate it using standard - // actions contributed by the workbench. - String showInLabel = IDEWorkbenchMessages.Workbench_showIn; - MenuManager showInSubMenu = new MenuManager(showInLabel); - showInSubMenu.add( - ContributionItemFactory.VIEWS_SHOW_IN.create( - mEditorDelegate.getEditor().getSite().getWorkbenchWindow())); - manager.add(showInSubMenu); - } - - /** - * Deletes the selection. Equivalent to pressing the Delete key. - */ - void delete() { - mDeleteAction.run(); - } - - /** - * Add new root in an existing empty XML layout. - * <p/> - * In case of error (unknown FQCN, document not empty), silently do nothing. - * In case of success, the new element will have some default attributes set - * (xmlns:android, layout_width and height). The edit is wrapped in a proper - * undo. - * <p/> - * This is invoked by - * {@link MoveGesture#drop(org.eclipse.swt.dnd.DropTargetEvent)}. - * - * @param root A non-null descriptor of the root element to create. - */ - void createDocumentRoot(final @NonNull SimpleElement root) { - String rootFqcn = root.getFqcn(); - - // Need a valid empty document to create the new root - final UiDocumentNode uiDoc = mEditorDelegate.getUiRootNode(); - if (uiDoc == null || uiDoc.getUiChildren().size() > 0) { - debugPrintf("Failed to create document root for %1$s: document is not empty", - rootFqcn); - return; - } - - // Find the view descriptor matching our FQCN - final ViewElementDescriptor viewDesc = mEditorDelegate.getFqcnViewDescriptor(rootFqcn); - if (viewDesc == null) { - // TODO this could happen if dropping a custom view not known in this project - debugPrintf("Failed to add document root, unknown FQCN %1$s", rootFqcn); - return; - } - - // Get the last segment of the FQCN for the undo title - String title = rootFqcn; - int pos = title.lastIndexOf('.'); - if (pos > 0 && pos < title.length() - 1) { - title = title.substring(pos + 1); - } - title = String.format("Create root %1$s in document", title); - - mEditorDelegate.getEditor().wrapUndoEditXmlModel(title, new Runnable() { - @Override - public void run() { - UiElementNode uiNew = uiDoc.appendNewUiChild(viewDesc); - - // A root node requires the Android XMLNS - uiNew.setAttributeValue( - SdkConstants.ANDROID_NS_NAME, - SdkConstants.XMLNS_URI, - SdkConstants.NS_RESOURCES, - true /*override*/); - - IDragAttribute[] attributes = root.getAttributes(); - if (attributes != null) { - for (IDragAttribute attribute : attributes) { - String uri = attribute.getUri(); - String name = attribute.getName(); - String value = attribute.getValue(); - uiNew.setAttributeValue(name, uri, value, false /*override*/); - } - } - - // Adjust the attributes - DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/); - - uiNew.createXmlNode(); - } - }); - } - - /** - * Returns the insets associated with views of the given fully qualified name, for the - * current theme and screen type. - * - * @param fqcn the fully qualified name to the widget type - * @return the insets, or null if unknown - */ - public Margins getInsets(String fqcn) { - if (ViewMetadataRepository.INSETS_SUPPORTED) { - ConfigurationChooser configComposite = getGraphicalEditor().getConfigurationChooser(); - String theme = configComposite.getThemeName(); - Density density = configComposite.getConfiguration().getDensity(); - return ViewMetadataRepository.getInsets(fqcn, density, theme); - } else { - return null; - } - } - - private void debugPrintf(String message, Object... params) { - if (DEBUG) { - AdtPlugin.printToConsole("Canvas", String.format(message, params)); - } - } - - /** The associated editor has been deactivated */ - public void deactivated() { - // Force the tooltip to be hidden. If you switch from the layout editor - // to a Java editor with the keyboard, the tooltip can stay open. - if (mLintTooltipManager != null) { - mLintTooltipManager.hide(); - } - } - - /** @see #setPreview(RenderPreview) */ - private RenderPreview mPreview; - - /** - * Sets the {@link RenderPreview} associated with the currently rendering - * configuration. - * <p> - * A {@link RenderPreview} has various additional state beyond its rendering, - * such as its display name (which can be edited by the user). When you click on - * previews, the layout editor switches to show the given configuration preview. - * The preview is then no longer shown in the list of previews and is instead rendered - * in the main editor. However, when you then switch away to some other preview, we - * want to be able to restore the preview with all its state. - * - * @param preview the preview associated with the current canvas - */ - public void setPreview(@Nullable RenderPreview preview) { - mPreview = preview; - } - - /** - * Returns the {@link RenderPreview} associated with this layout canvas. - * - * @see #setPreview(RenderPreview) - * @return the {@link RenderPreview} - */ - @Nullable - public RenderPreview getPreview() { - return mPreview; - } - - /** Ensures that the configuration previews are up to date for this canvas */ - public void syncPreviewMode() { - if (mImageOverlay != null && mImageOverlay.getImage() != null && - getGraphicalEditor().getConfigurationChooser().getResources() != null) { - if (mPreviewManager.recomputePreviews(false)) { - // Zoom when syncing modes - mZoomFitNextImage = true; - ensureZoomed(); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java deleted file mode 100644 index e349a1cb0..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2010 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.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; - -import org.eclipse.core.runtime.ListenerList; -import org.eclipse.jface.util.SafeRunnable; -import org.eclipse.jface.viewers.IPostSelectionProvider; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.jface.viewers.TreeSelection; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - - -/** - * JFace {@link Viewer} wrapper around {@link LayoutCanvas}. - * <p/> - * The viewer is owned by {@link GraphicalEditorPart}. - * <p/> - * The viewer is an {@link ISelectionProvider} instance and is set as the - * site's main {@link ISelectionProvider} by the editor part. Consequently - * canvas' selection changes are broadcasted to anyone listening, which includes - * the part itself as well as the associated outline and property sheet pages. - */ -class LayoutCanvasViewer extends Viewer implements IPostSelectionProvider { - - private LayoutCanvas mCanvas; - private final LayoutEditorDelegate mEditorDelegate; - - public LayoutCanvasViewer(LayoutEditorDelegate editorDelegate, - RulesEngine rulesEngine, - Composite parent, - int style) { - mEditorDelegate = editorDelegate; - mCanvas = new LayoutCanvas(editorDelegate, rulesEngine, parent, style); - - mCanvas.getSelectionManager().addSelectionChangedListener(mSelectionListener); - } - - private ISelectionChangedListener mSelectionListener = new ISelectionChangedListener() { - @Override - public void selectionChanged(SelectionChangedEvent event) { - fireSelectionChanged(event); - firePostSelectionChanged(event); - } - }; - - @Override - public Control getControl() { - return mCanvas; - } - - /** - * Returns the underlying {@link LayoutCanvas}. - * This is the same control as returned by {@link #getControl()} but clients - * have it already casted in the right type. - * <p/> - * This can never be null. - * @return The underlying {@link LayoutCanvas}. - */ - public LayoutCanvas getCanvas() { - return mCanvas; - } - - /** - * Returns the current layout editor's input. - */ - @Override - public Object getInput() { - return mEditorDelegate.getEditor().getEditorInput(); - } - - /** - * Unused. We don't support switching the input. - */ - @Override - public void setInput(Object input) { - } - - /** - * Returns a new {@link TreeSelection} where each {@link TreePath} item - * is a {@link CanvasViewInfo}. - */ - @Override - public ISelection getSelection() { - return mCanvas.getSelectionManager().getSelection(); - } - - /** - * Sets a new selection. <code>reveal</code> is ignored right now. - * <p/> - * The selection can be null, which is interpreted as an empty selection. - */ - @Override - public void setSelection(ISelection selection, boolean reveal) { - if (mEditorDelegate.getEditor().getIgnoreXmlUpdate()) { - return; - } - mCanvas.getSelectionManager().setSelection(selection); - } - - /** Unused. Refreshing is done solely by the owning {@link LayoutEditorDelegate}. */ - @Override - public void refresh() { - // ignore - } - - public void dispose() { - if (mSelectionListener != null) { - mCanvas.getSelectionManager().removeSelectionChangedListener(mSelectionListener); - } - if (mCanvas != null) { - mCanvas.dispose(); - mCanvas = null; - } - } - - // ---- Implements IPostSelectionProvider ---- - - private ListenerList mPostChangedListeners = new ListenerList(); - - @Override - public void addPostSelectionChangedListener(ISelectionChangedListener listener) { - mPostChangedListeners.add(listener); - } - - @Override - public void removePostSelectionChangedListener(ISelectionChangedListener listener) { - mPostChangedListeners.remove(listener); - } - - protected void firePostSelectionChanged(final SelectionChangedEvent event) { - Object[] listeners = mPostChangedListeners.getListeners(); - for (int i = 0; i < listeners.length; i++) { - final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i]; - SafeRunnable.run(new SafeRunnable() { - @Override - public void run() { - l.selectionChanged(event); - } - }); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java deleted file mode 100644 index b79e3b0a1..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java +++ /dev/null @@ -1,413 +0,0 @@ -/* - * 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.SdkConstants.ANDROID_LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_NUM_COLUMNS; -import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW; -import static com.android.SdkConstants.GRID_VIEW; -import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.TOOLS_URI; -import static com.android.SdkConstants.VALUE_AUTO_FIT; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.rendering.api.AdapterBinding; -import com.android.ide.common.rendering.api.DataBindingItem; -import com.android.ide.common.rendering.api.ResourceReference; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.swt.widgets.Display; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.progress.WorkbenchJob; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * Design-time metadata lookup for layouts, such as fragment and AdapterView bindings. - */ -public class LayoutMetadata { - /** The default layout to use for list items in expandable list views */ - public static final String DEFAULT_EXPANDABLE_LIST_ITEM = "simple_expandable_list_item_2"; //$NON-NLS-1$ - /** The default layout to use for list items in plain list views */ - public static final String DEFAULT_LIST_ITEM = "simple_list_item_2"; //$NON-NLS-1$ - /** The default layout to use for list items in spinners */ - public static final String DEFAULT_SPINNER_ITEM = "simple_spinner_item"; //$NON-NLS-1$ - - /** The string to start metadata comments with */ - private static final String COMMENT_PROLOGUE = " Preview: "; - /** The property key, included in comments, which references a list item layout */ - public static final String KEY_LV_ITEM = "listitem"; //$NON-NLS-1$ - /** The property key, included in comments, which references a list header layout */ - public static final String KEY_LV_HEADER = "listheader"; //$NON-NLS-1$ - /** The property key, included in comments, which references a list footer layout */ - public static final String KEY_LV_FOOTER = "listfooter"; //$NON-NLS-1$ - /** The property key, included in comments, which references a fragment layout to show */ - public static final String KEY_FRAGMENT_LAYOUT = "layout"; //$NON-NLS-1$ - // NOTE: If you add additional keys related to resources, make sure you update the - // ResourceRenameParticipant - - /** Utility class, do not create instances */ - private LayoutMetadata() { - } - - /** - * Returns the given property specified in the <b>current</b> element being - * processed by the given pull parser. - * - * @param parser the pull parser, which must be in the middle of processing - * the target element - * @param name the property name to look up - * @return the property value, or null if not defined - */ - @Nullable - public static String getProperty(@NonNull XmlPullParser parser, @NonNull String name) { - String value = parser.getAttributeValue(TOOLS_URI, name); - if (value != null && value.isEmpty()) { - value = null; - } - - return value; - } - - /** - * Clears the old metadata from the given node - * - * @param node the XML node to associate metadata with - * @deprecated this method clears metadata using the old comment-based style; - * should only be used for migration at this point - */ - @Deprecated - public static void clearLegacyComment(Node node) { - NodeList children = node.getChildNodes(); - for (int i = 0, n = children.getLength(); i < n; i++) { - Node child = children.item(i); - if (child.getNodeType() == Node.COMMENT_NODE) { - String text = child.getNodeValue(); - if (text.startsWith(COMMENT_PROLOGUE)) { - Node commentNode = child; - // Remove the comment, along with surrounding whitespace if applicable - Node previous = commentNode.getPreviousSibling(); - if (previous != null && previous.getNodeType() == Node.TEXT_NODE) { - if (previous.getNodeValue().trim().length() == 0) { - node.removeChild(previous); - } - } - node.removeChild(commentNode); - Node first = node.getFirstChild(); - if (first != null && first.getNextSibling() == null - && first.getNodeType() == Node.TEXT_NODE) { - if (first.getNodeValue().trim().length() == 0) { - node.removeChild(first); - } - } - } - } - } - } - - /** - * Returns the given property of the given DOM node, or null - * - * @param node the XML node to associate metadata with - * @param name the name of the property to look up - * @return the value stored with the given node and name, or null - */ - @Nullable - public static String getProperty( - @NonNull Node node, - @NonNull String name) { - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - String value = element.getAttributeNS(TOOLS_URI, name); - if (value != null && value.isEmpty()) { - value = null; - } - - return value; - } - - return null; - } - - /** - * Sets the given property of the given DOM node to a given value, or if null clears - * the property. - * - * @param editor the editor associated with the property - * @param node the XML node to associate metadata with - * @param name the name of the property to set - * @param value the value to store for the given node and name, or null to remove it - */ - public static void setProperty( - @NonNull final AndroidXmlEditor editor, - @NonNull final Node node, - @NonNull final String name, - @Nullable final String value) { - // Clear out the old metadata - clearLegacyComment(node); - - if (node.getNodeType() == Node.ELEMENT_NODE) { - final Element element = (Element) node; - final String undoLabel = "Bind View"; - AdtUtils.setToolsAttribute(editor, element, undoLabel, name, value, - false /*reveal*/, false /*append*/); - - // Also apply the same layout to any corresponding elements in other configurations - // of this layout. - final IFile file = editor.getInputFile(); - if (file != null) { - final List<IFile> variations = AdtUtils.getResourceVariations(file, false); - if (variations.isEmpty()) { - return; - } - Display display = AdtPlugin.getDisplay(); - WorkbenchJob job = new WorkbenchJob(display, "Update alternate views") { - @Override - public IStatus runInUIThread(IProgressMonitor monitor) { - for (IFile variation : variations) { - if (variation.equals(file)) { - continue; - } - try { - // If the corresponding file is open in the IDE, use the - // editor version instead - if (!AdtPrefs.getPrefs().isSharedLayoutEditor()) { - if (setPropertyInEditor(undoLabel, variation, element, name, - value)) { - return Status.OK_STATUS; - } - } - - boolean old = editor.getIgnoreXmlUpdate(); - try { - editor.setIgnoreXmlUpdate(true); - setPropertyInFile(undoLabel, variation, element, name, value); - } finally { - editor.setIgnoreXmlUpdate(old); - } - } catch (Exception e) { - AdtPlugin.log(e, variation.getFullPath().toOSString()); - } - } - return Status.OK_STATUS; - } - - }; - job.setSystem(true); - job.schedule(); - } - } - } - - private static boolean setPropertyInEditor( - @NonNull String undoLabel, - @NonNull IFile variation, - @NonNull final Element equivalentElement, - @NonNull final String name, - @Nullable final String value) { - Collection<IEditorPart> editors = - AdtUtils.findEditorsFor(variation, false /*restore*/); - for (IEditorPart part : editors) { - AndroidXmlEditor editor = AdtUtils.getXmlEditor(part); - if (editor != null) { - Document doc = DomUtilities.getDocument(editor); - if (doc != null) { - Element element = DomUtilities.findCorresponding(equivalentElement, doc); - if (element != null) { - AdtUtils.setToolsAttribute(editor, element, undoLabel, name, - value, false /*reveal*/, false /*append*/); - if (part instanceof GraphicalEditorPart) { - GraphicalEditorPart g = (GraphicalEditorPart) part; - g.recomputeLayout(); - g.getCanvasControl().redraw(); - } - return true; - } - } - } - } - - return false; - } - - private static boolean setPropertyInFile( - @NonNull String undoLabel, - @NonNull IFile variation, - @NonNull final Element element, - @NonNull final String name, - @Nullable final String value) { - Document doc = DomUtilities.getDocument(variation); - if (doc != null && element.getOwnerDocument() != doc) { - Element other = DomUtilities.findCorresponding(element, doc); - if (other != null) { - AdtUtils.setToolsAttribute(variation, other, undoLabel, - name, value, false); - - return true; - } - } - - return false; - } - - /** Strips out @layout/ or @android:layout/ from the given layout reference */ - private static String stripLayoutPrefix(String layout) { - if (layout.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX)) { - layout = layout.substring(ANDROID_LAYOUT_RESOURCE_PREFIX.length()); - } else if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) { - layout = layout.substring(LAYOUT_RESOURCE_PREFIX.length()); - } - - return layout; - } - - /** - * Creates an {@link AdapterBinding} for the given view object, or null if the user - * has not yet chosen a target layout to use for the given AdapterView. - * - * @param viewObject the view object to create an adapter binding for - * @param map a map containing tools attribute metadata - * @return a binding, or null - */ - @Nullable - public static AdapterBinding getNodeBinding( - @Nullable Object viewObject, - @NonNull Map<String, String> map) { - String header = map.get(KEY_LV_HEADER); - String footer = map.get(KEY_LV_FOOTER); - String layout = map.get(KEY_LV_ITEM); - if (layout != null || header != null || footer != null) { - int count = 12; - return getNodeBinding(viewObject, header, footer, layout, count); - } - - return null; - } - - /** - * Creates an {@link AdapterBinding} for the given view object, or null if the user - * has not yet chosen a target layout to use for the given AdapterView. - * - * @param viewObject the view object to create an adapter binding for - * @param uiNode the ui node corresponding to the view object - * @return a binding, or null - */ - @Nullable - public static AdapterBinding getNodeBinding( - @Nullable Object viewObject, - @NonNull UiViewElementNode uiNode) { - Node xmlNode = uiNode.getXmlNode(); - - String header = getProperty(xmlNode, KEY_LV_HEADER); - String footer = getProperty(xmlNode, KEY_LV_FOOTER); - String layout = getProperty(xmlNode, KEY_LV_ITEM); - if (layout != null || header != null || footer != null) { - int count = 12; - // If we're dealing with a grid view, multiply the list item count - // by the number of columns to ensure we have enough items - if (xmlNode instanceof Element && xmlNode.getNodeName().endsWith(GRID_VIEW)) { - Element element = (Element) xmlNode; - String columns = element.getAttributeNS(ANDROID_URI, ATTR_NUM_COLUMNS); - int multiplier = 2; - if (columns != null && columns.length() > 0 && - !columns.equals(VALUE_AUTO_FIT)) { - try { - int c = Integer.parseInt(columns); - if (c >= 1 && c <= 10) { - multiplier = c; - } - } catch (NumberFormatException nufe) { - // some unexpected numColumns value: just stick with 2 columns for - // preview purposes - } - } - count *= multiplier; - } - - return getNodeBinding(viewObject, header, footer, layout, count); - } - - return null; - } - - private static AdapterBinding getNodeBinding(Object viewObject, - String header, String footer, String layout, int count) { - if (layout != null || header != null || footer != null) { - AdapterBinding binding = new AdapterBinding(count); - - if (header != null) { - boolean isFramework = header.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX); - binding.addHeader(new ResourceReference(stripLayoutPrefix(header), - isFramework)); - } - - if (footer != null) { - boolean isFramework = footer.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX); - binding.addFooter(new ResourceReference(stripLayoutPrefix(footer), - isFramework)); - } - - if (layout != null) { - boolean isFramework = layout.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX); - if (isFramework) { - layout = layout.substring(ANDROID_LAYOUT_RESOURCE_PREFIX.length()); - } else if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) { - layout = layout.substring(LAYOUT_RESOURCE_PREFIX.length()); - } - - binding.addItem(new DataBindingItem(layout, isFramework, 1)); - } else if (viewObject != null) { - String listFqcn = ProjectCallback.getListAdapterViewFqcn(viewObject.getClass()); - if (listFqcn != null) { - if (listFqcn.endsWith(EXPANDABLE_LIST_VIEW)) { - binding.addItem( - new DataBindingItem(DEFAULT_EXPANDABLE_LIST_ITEM, - true /* isFramework */, 1)); - } else { - binding.addItem( - new DataBindingItem(DEFAULT_LIST_ITEM, - true /* isFramework */, 1)); - } - } - } else { - binding.addItem( - new DataBindingItem(DEFAULT_LIST_ITEM, - true /* isFramework */, 1)); - } - return binding; - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutPoint.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutPoint.java deleted file mode 100644 index 818b2c4ef..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutPoint.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2010 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.common.api.Point; - -import org.eclipse.swt.dnd.DragSourceEvent; -import org.eclipse.swt.dnd.DragSourceListener; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; - -/** - * A {@link LayoutPoint} is a coordinate in the Android canvas (in other words, - * it may differ from the canvas control mouse coordinate because the canvas may - * be zoomed and scrolled.) - */ -public final class LayoutPoint { - /** Containing canvas which the point is relative to. */ - private final LayoutCanvas mCanvas; - - /** The X coordinate of the canvas coordinate. */ - public final int x; - - /** The Y coordinate of the canvas coordinate. */ - public final int y; - - /** - * Constructs a new {@link LayoutPoint} from the given event. The event - * must be from a {@link MouseListener} associated with the - * {@link LayoutCanvas} such that the {@link MouseEvent#x} and - * {@link MouseEvent#y} fields are relative to the canvas. - * - * @param canvas The {@link LayoutCanvas} this point is within. - * @param event The mouse event to construct the {@link LayoutPoint} - * from. - * @return A {@link LayoutPoint} which corresponds to the given - * {@link MouseEvent}. - */ - public static LayoutPoint create(LayoutCanvas canvas, MouseEvent event) { - // The mouse event coordinates should already be relative to the canvas - // widget. - assert event.widget == canvas : event.widget; - return ControlPoint.create(canvas, event).toLayout(); - } - - /** - * Constructs a new {@link LayoutPoint} from the given event. The event - * must be from a {@link DragSourceListener} associated with the - * {@link LayoutCanvas} such that the {@link DragSourceEvent#x} and - * {@link DragSourceEvent#y} fields are relative to the canvas. - * - * @param canvas The {@link LayoutCanvas} this point is within. - * @param event The mouse event to construct the {@link LayoutPoint} - * from. - * @return A {@link LayoutPoint} which corresponds to the given - * {@link DragSourceEvent}. - */ - public static LayoutPoint create(LayoutCanvas canvas, DragSourceEvent event) { - // The drag source event coordinates should already be relative to the - // canvas widget. - return ControlPoint.create(canvas, event).toLayout(); - } - - /** - * Constructs a new {@link LayoutPoint} from the given x,y coordinates. - * - * @param canvas The {@link LayoutCanvas} this point is within. - * @param x The mouse event x coordinate relative to the canvas - * @param y The mouse event x coordinate relative to the canvas - * @return A {@link LayoutPoint} which corresponds to the given - * layout coordinates. - */ - public static LayoutPoint create(LayoutCanvas canvas, int x, int y) { - return new LayoutPoint(canvas, x, y); - } - - /** - * Constructs a new {@link LayoutPoint} with the given X and Y coordinates. - * - * @param canvas The canvas which contains this coordinate - * @param x The canvas X coordinate - * @param y The canvas Y coordinate - */ - private LayoutPoint(LayoutCanvas canvas, int x, int y) { - mCanvas = canvas; - this.x = x; - this.y = y; - } - - /** - * Returns the equivalent {@link ControlPoint} to this - * {@link LayoutPoint}. - * - * @return The equivalent {@link ControlPoint} to this - * {@link LayoutPoint} - */ - public ControlPoint toControl() { - int cx = mCanvas.getHorizontalTransform().translate(x); - int cy = mCanvas.getVerticalTransform().translate(y); - - return ControlPoint.create(mCanvas, cx, cy); - } - - /** - * Returns this {@link LayoutPoint} as a {@link Point}, in the same coordinate space. - * - * @return a new {@link Point} in the same coordinate space - */ - public Point toPoint() { - return new Point(x, y); - } - - @Override - public String toString() { - return "LayoutPoint [x=" + x + ", y=" + y + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + x; - result = prime * result + y; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - LayoutPoint other = (LayoutPoint) obj; - if (x != other.x) - return false; - if (y != other.y) - return false; - return true; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java deleted file mode 100644 index 56b86aa85..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.google.common.collect.Maps; - -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IEditorReference; -import org.eclipse.ui.IPartListener2; -import org.eclipse.ui.IPartService; -import org.eclipse.ui.IViewReference; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchPart; -import org.eclipse.ui.IWorkbenchPartReference; -import org.eclipse.ui.IWorkbenchWindow; - -import java.util.Map; - -/** - * The {@link LayoutWindowCoordinator} keeps track of Eclipse window events (opening, closing, - * fronting, etc) and uses this information to manage the propertysheet and outline - * views such that they are always(*) showing: - * <ul> - * <li> If the Property Sheet and Outline Eclipse views are showing, it does nothing. - * "Showing" means "is open", not necessary "is visible", e.g. in a tabbed view - * there could be a different view on top. - * <li> If just the outline is showing, then the property sheet is shown in a sashed - * pane below or to the right of the outline (depending on the dominant dimension - * of the window). - * <li> TBD: If just the property sheet is showing, should the outline be showed - * inside that window? Not yet done. - * <li> If the outline is *not* showing, then the outline is instead shown - * <b>inside</b> the editor area, in a right-docked view! This right docked view - * also includes the property sheet! - * <li> If the property sheet is not showing (which includes not showing in the outline - * view as well), then it will be shown inside the editor area, along with the outline - * which should also be there (since if the outline was showing outside the editor - * area, the property sheet would have docked there). - * <li> When the editor is maximized, then all views are temporarily hidden. In this - * case, the property sheet and outline will show up inside the editor. - * When the editor view is un-maximized, the view state will return to what it - * was before. - * </ul> - * </p> - * There is one coordinator per workbench window, shared between all editors in that window. - * <p> - * TODO: Rename this class to AdtWindowCoordinator. It is used for more than just layout - * window coordination now. For example, it's also used to dispatch {@code activated()} and - * {@code deactivated()} events to all the XML editors, to ensure that key bindings are - * properly dispatched to the right editors in Eclipse 4.x. - */ -public class LayoutWindowCoordinator implements IPartListener2 { - static final String PROPERTY_SHEET_PART_ID = "org.eclipse.ui.views.PropertySheet"; //$NON-NLS-1$ - static final String OUTLINE_PART_ID = "org.eclipse.ui.views.ContentOutline"; //$NON-NLS-1$ - /** The workbench window */ - private final IWorkbenchWindow mWindow; - /** Is the Eclipse property sheet ViewPart open? */ - private boolean mPropertiesOpen; - /** Is the Eclipse outline ViewPart open? */ - private boolean mOutlineOpen; - /** Is the editor maximized? */ - private boolean mEditorMaximized; - /** - * Has the coordinator been initialized? We may have to delay initialization - * and perform it lazily if the workbench window does not have an active - * page when the coordinator is first started - */ - private boolean mInitialized; - - /** Map from workbench windows to each layout window coordinator instance for that window */ - private static Map<IWorkbenchWindow, LayoutWindowCoordinator> sCoordinators = - Maps.newHashMapWithExpectedSize(2); - - /** - * Returns the coordinator for the given window. - * - * @param window the associated window - * @param create whether to create the window if it does not already exist - * @return the new coordinator, never null if {@code create} is true - */ - @Nullable - public static LayoutWindowCoordinator get(@NonNull IWorkbenchWindow window, boolean create) { - synchronized (LayoutWindowCoordinator.class){ - LayoutWindowCoordinator coordinator = sCoordinators.get(window); - if (coordinator == null && create) { - coordinator = new LayoutWindowCoordinator(window); - - IPartService service = window.getPartService(); - if (service != null) { - // What if the editor part is *already* open? How do I deal with that? - service.addPartListener(coordinator); - } - - sCoordinators.put(window, coordinator); - } - - return coordinator; - } - } - - - /** Disposes this coordinator (when a window is closed) */ - public void dispose() { - IPartService service = mWindow.getPartService(); - if (service != null) { - service.removePartListener(this); - } - - synchronized (LayoutWindowCoordinator.class){ - sCoordinators.remove(mWindow); - } - } - - /** - * Returns true if the main editor window is maximized - * - * @return true if the main editor window is maximized - */ - public boolean isEditorMaximized() { - return mEditorMaximized; - } - - private LayoutWindowCoordinator(@NonNull IWorkbenchWindow window) { - mWindow = window; - - initialize(); - } - - private void initialize() { - if (mInitialized) { - return; - } - - IWorkbenchPage activePage = mWindow.getActivePage(); - if (activePage == null) { - return; - } - - mInitialized = true; - - // Look up current state of the properties and outline windows (in case - // they have already been opened before we added our part listener) - IViewReference ref = findPropertySheetView(activePage); - if (ref != null) { - IWorkbenchPart part = ref.getPart(false /*restore*/); - if (activePage.isPartVisible(part)) { - mPropertiesOpen = true; - } - } - ref = findOutlineView(activePage); - if (ref != null) { - IWorkbenchPart part = ref.getPart(false /*restore*/); - if (activePage.isPartVisible(part)) { - mOutlineOpen = true; - } - } - if (!syncMaximizedState(activePage)) { - syncActive(); - } - } - - static IViewReference findPropertySheetView(IWorkbenchPage activePage) { - return activePage.findViewReference(PROPERTY_SHEET_PART_ID); - } - - static IViewReference findOutlineView(IWorkbenchPage activePage) { - return activePage.findViewReference(OUTLINE_PART_ID); - } - - /** - * Checks the maximized state of the page and updates internal state if - * necessary. - * <p> - * This is used in Eclipse 4.x, where the {@link IPartListener2} does not - * fire {@link IPartListener2#partHidden(IWorkbenchPartReference)} when the - * editor is maximized anymore (see issue - * https://bugs.eclipse.org/bugs/show_bug.cgi?id=382120 for details). - * Instead, the layout editor listens for resize events, and upon resize it - * looks up the part state and calls this method to ensure that the right - * maximized state is known to the layout coordinator. - * - * @param page the active workbench page - * @return true if the state changed, false otherwise - */ - public boolean syncMaximizedState(IWorkbenchPage page) { - boolean maximized = isPageZoomed(page); - if (mEditorMaximized != maximized) { - mEditorMaximized = maximized; - syncActive(); - return true; - } - return false; - } - - private boolean isPageZoomed(IWorkbenchPage page) { - IWorkbenchPartReference reference = page.getActivePartReference(); - if (reference != null && reference instanceof IEditorReference) { - int state = page.getPartState(reference); - boolean maximized = (state & IWorkbenchPage.STATE_MAXIMIZED) != 0; - return maximized; - } - - // If the active reference isn't the editor, then the editor can't be maximized - return false; - } - - /** - * Syncs the given editor's view state such that the property sheet and or - * outline are shown or hidden according to the visibility of the global - * outline and property sheet views. - * <p> - * This is typically done when a layout editor is fronted. For view updates - * when the view is already showing, the {@link LayoutWindowCoordinator} - * will automatically handle the current fronted window. - * - * @param editor the editor to sync - */ - private void sync(@Nullable GraphicalEditorPart editor) { - if (editor == null) { - return; - } - if (mEditorMaximized) { - editor.showStructureViews(true /*outline*/, true /*properties*/, true /*layout*/); - } else if (mOutlineOpen) { - editor.showStructureViews(false /*outline*/, false /*properties*/, true /*layout*/); - editor.getCanvasControl().getOutlinePage().setShowPropertySheet(!mPropertiesOpen); - } else { - editor.showStructureViews(true /*outline*/, !mPropertiesOpen /*properties*/, - true /*layout*/); - } - } - - private void sync(IWorkbenchPart part) { - if (part instanceof AndroidXmlEditor) { - LayoutEditorDelegate editor = LayoutEditorDelegate.fromEditor((IEditorPart) part); - if (editor != null) { - sync(editor.getGraphicalEditor()); - } - } - } - - private void syncActive() { - IWorkbenchPage activePage = mWindow.getActivePage(); - if (activePage != null) { - IEditorPart editor = activePage.getActiveEditor(); - sync(editor); - } - } - - private void propertySheetClosed() { - mPropertiesOpen = false; - syncActive(); - } - - private void propertySheetOpened() { - mPropertiesOpen = true; - syncActive(); - } - - private void outlineClosed() { - mOutlineOpen = false; - syncActive(); - } - - private void outlineOpened() { - mOutlineOpen = true; - syncActive(); - } - - // ---- Implements IPartListener2 ---- - - @Override - public void partOpened(IWorkbenchPartReference partRef) { - // We ignore partOpened() and partClosed() because these methods are only - // called when a view is opened in the first perspective, and closed in the - // last perspective. The outline is typically used in multiple perspectives, - // so closing it in the Java perspective does *not* fire a partClosed event. - // There is no notification for "part closed in perspective" (see issue - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=54559 for details). - // However, the workaround we can use is to listen to partVisible() and - // partHidden(). These will be called more often than we'd like (e.g. - // when the tab order causes a view to be obscured), however, we can use - // the workaround of looking up IWorkbenchPage.findViewReference(id) after - // partHidden(), which will return null if the view is closed in the current - // perspective. For partOpened, we simply look in partVisible() for whether - // our flags tracking the view state have been initialized already. - } - - @Override - public void partClosed(IWorkbenchPartReference partRef) { - // partClosed() doesn't get called when a window is closed unless it has - // been closed in *all* perspectives. See partOpened() for more. - } - - @Override - public void partHidden(IWorkbenchPartReference partRef) { - IWorkbenchPage activePage = mWindow.getActivePage(); - if (activePage == null) { - return; - } - initialize(); - - // See if this looks like the window was closed in this workspace - // See partOpened() for an explanation. - String id = partRef.getId(); - if (PROPERTY_SHEET_PART_ID.equals(id)) { - if (activePage.findViewReference(id) == null) { - propertySheetClosed(); - return; - } - } else if (OUTLINE_PART_ID.equals(id)) { - if (activePage.findViewReference(id) == null) { - outlineClosed(); - return; - } - } - - // Does this look like a window getting maximized? - syncMaximizedState(activePage); - } - - @Override - public void partVisible(IWorkbenchPartReference partRef) { - IWorkbenchPage activePage = mWindow.getActivePage(); - if (activePage == null) { - return; - } - initialize(); - - String id = partRef.getId(); - if (mEditorMaximized) { - // Return to their non-maximized state - mEditorMaximized = false; - syncActive(); - } - - IWorkbenchPart part = partRef.getPart(false /*restore*/); - sync(part); - - // See partOpened() for an explanation - if (PROPERTY_SHEET_PART_ID.equals(id)) { - if (!mPropertiesOpen) { - propertySheetOpened(); - assert mPropertiesOpen; - } - } else if (OUTLINE_PART_ID.equals(id)) { - if (!mOutlineOpen) { - outlineOpened(); - assert mOutlineOpen; - } - } - } - - @Override - public void partInputChanged(IWorkbenchPartReference partRef) { - } - - @Override - public void partActivated(IWorkbenchPartReference partRef) { - IWorkbenchPart part = partRef.getPart(false); - if (part instanceof AndroidXmlEditor) { - ((AndroidXmlEditor)part).activated(); - } - } - - @Override - public void partBroughtToTop(IWorkbenchPartReference partRef) { - } - - @Override - public void partDeactivated(IWorkbenchPartReference partRef) { - IWorkbenchPart part = partRef.getPart(false); - if (part instanceof AndroidXmlEditor) { - ((AndroidXmlEditor)part).deactivated(); - } - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java deleted file mode 100644 index ca74493e8..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; -import com.google.common.collect.Lists; - -import org.eclipse.core.resources.IMarker; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Rectangle; -import org.w3c.dom.Node; - -import java.util.Collection; - -/** - * The {@link LintOverlay} paints an icon over each view that contains at least one - * lint error (unless the view is smaller than the icon) - */ -public class LintOverlay extends Overlay { - /** Approximate size of lint overlay icons */ - static final int ICON_SIZE = 8; - /** Alpha to draw lint overlay icons with */ - private static final int ALPHA = 192; - - private final LayoutCanvas mCanvas; - private Image mWarningImage; - private Image mErrorImage; - - /** - * Constructs a new {@link LintOverlay} - * - * @param canvas the associated canvas - */ - public LintOverlay(LayoutCanvas canvas) { - mCanvas = canvas; - } - - @Override - public boolean isHiding() { - return super.isHiding() || !AdtPrefs.getPrefs().isLintOnSave(); - } - - @Override - public void paint(GC gc) { - LayoutEditorDelegate editor = mCanvas.getEditorDelegate(); - Collection<Node> nodes = editor.getLintNodes(); - if (nodes != null && !nodes.isEmpty()) { - // Copy list before iterating through it to avoid a concurrent list modification - // in case lint runs in the background while painting and updates this list - nodes = Lists.newArrayList(nodes); - ViewHierarchy hierarchy = mCanvas.getViewHierarchy(); - Image icon = getWarningIcon(); - ImageData imageData = icon.getImageData(); - int iconWidth = imageData.width; - int iconHeight = imageData.height; - CanvasTransform mHScale = mCanvas.getHorizontalTransform(); - CanvasTransform mVScale = mCanvas.getVerticalTransform(); - - // Right/bottom edges of the canvas image; don't paint overlays outside of - // that. (With for example RelativeLayouts with margins rendered on smaller - // screens than they are intended for this can happen.) - int maxX = mHScale.translate(0) + mHScale.getScaledImgSize(); - int maxY = mVScale.translate(0) + mVScale.getScaledImgSize(); - - int oldAlpha = gc.getAlpha(); - try { - gc.setAlpha(ALPHA); - for (Node node : nodes) { - CanvasViewInfo vi = hierarchy.findViewInfoFor(node); - if (vi != null) { - Rectangle bounds = vi.getAbsRect(); - int x = mHScale.translate(bounds.x); - int y = mVScale.translate(bounds.y); - int w = mHScale.scale(bounds.width); - int h = mVScale.scale(bounds.height); - if (w < iconWidth || h < iconHeight) { - // Don't draw badges on tiny widgets (including those - // that aren't tiny but are zoomed out too far) - continue; - } - - x += w - iconWidth; - y += h - iconHeight; - - if (x > maxX || y > maxY) { - continue; - } - - boolean isError = false; - IMarker marker = editor.getIssueForNode(vi.getUiViewNode()); - if (marker != null) { - int severity = marker.getAttribute(IMarker.SEVERITY, 0); - isError = severity == IMarker.SEVERITY_ERROR; - } - - icon = isError ? getErrorIcon() : getWarningIcon(); - - gc.drawImage(icon, x, y); - } - } - } finally { - gc.setAlpha(oldAlpha); - } - } - } - - private Image getWarningIcon() { - if (mWarningImage == null) { - mWarningImage = IconFactory.getInstance().getIcon("warning-badge"); //$NON-NLS-1$ - } - - return mWarningImage; - } - - private Image getErrorIcon() { - if (mErrorImage == null) { - mErrorImage = IconFactory.getInstance().getIcon("error-badge"); //$NON-NLS-1$ - } - - return mErrorImage; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java deleted file mode 100644 index cedd43659..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.ATTR_ID; - -import com.android.ide.common.layout.BaseLayoutRule; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; - -import org.eclipse.core.resources.IMarker; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; - -import java.util.List; - -/** Actual tooltip showing multiple lines for various widgets that have lint errors */ -class LintTooltip extends Shell { - private final LayoutCanvas mCanvas; - private final List<UiViewElementNode> mNodes; - - LintTooltip(LayoutCanvas canvas, List<UiViewElementNode> nodes) { - super(canvas.getDisplay(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); - mCanvas = canvas; - mNodes = nodes; - - createContents(); - } - - protected void createContents() { - Display display = getDisplay(); - Color fg = display.getSystemColor(SWT.COLOR_INFO_FOREGROUND); - Color bg = display.getSystemColor(SWT.COLOR_INFO_BACKGROUND); - setBackground(bg); - GridLayout gridLayout = new GridLayout(2, false); - setLayout(gridLayout); - - LayoutEditorDelegate delegate = mCanvas.getEditorDelegate(); - - boolean first = true; - for (UiViewElementNode node : mNodes) { - IMarker marker = delegate.getIssueForNode(node); - if (marker != null) { - String message = marker.getAttribute(IMarker.MESSAGE, null); - if (message != null) { - Label icon = new Label(this, SWT.NONE); - icon.setForeground(fg); - icon.setBackground(bg); - icon.setImage(node.getIcon()); - - Label label = new Label(this, SWT.WRAP); - if (first) { - label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1)); - first = false; - } - - String id = BaseLayoutRule.stripIdPrefix(node.getAttributeValue(ATTR_ID)); - if (id.isEmpty()) { - if (node.getXmlNode() != null) { - id = node.getXmlNode().getNodeName(); - } else { - id = node.getDescriptor().getUiName(); - } - } - - label.setText(String.format("%1$s: %2$s", id, message)); - } - } - } - } - - @Override - protected void checkSubclass() { - // Disable the check that prevents subclassing of SWT components - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java deleted file mode 100644 index f71935889..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LintOverlay.ICON_SIZE; - -import com.android.annotations.Nullable; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Shell; -import org.w3c.dom.Node; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** Tooltip in the layout editor showing lint errors under the cursor */ -class LintTooltipManager implements Listener { - private final LayoutCanvas mCanvas; - private Shell mTip = null; - private List<UiViewElementNode> mShowingNodes; - - /** - * Sets up a custom tooltip when hovering over tree items. It currently displays the error - * message for the lint warning associated with each node, if any (and only if the hover - * is over the icon portion). - */ - LintTooltipManager(LayoutCanvas canvas) { - mCanvas = canvas; - } - - void register() { - mCanvas.addListener(SWT.Dispose, this); - mCanvas.addListener(SWT.KeyDown, this); - mCanvas.addListener(SWT.MouseMove, this); - mCanvas.addListener(SWT.MouseHover, this); - } - - void unregister() { - if (!mCanvas.isDisposed()) { - mCanvas.removeListener(SWT.Dispose, this); - mCanvas.removeListener(SWT.KeyDown, this); - mCanvas.removeListener(SWT.MouseMove, this); - mCanvas.removeListener(SWT.MouseHover, this); - } - } - - @Override - public void handleEvent(Event event) { - switch(event.type) { - case SWT.MouseMove: - // See if we're still overlapping this or *other* errors; if so, keep the - // tip up (or update it). - if (mShowingNodes != null) { - List<UiViewElementNode> nodes = computeNodes(event); - if (nodes != null && !nodes.isEmpty()) { - if (nodes.equals(mShowingNodes)) { - return; - } else { - show(nodes); - } - break; - } - } - - // If not, fall through and hide the tooltip - - //$FALL-THROUGH$ - case SWT.Dispose: - case SWT.FocusOut: - case SWT.KeyDown: - case SWT.MouseExit: - case SWT.MouseDown: - hide(); - break; - case SWT.MouseHover: - hide(); - show(event); - break; - } - } - - void hide() { - if (mTip != null) { - mTip.dispose(); - mTip = null; - } - mShowingNodes = null; - } - - private void show(Event event) { - List<UiViewElementNode> nodes = computeNodes(event); - if (nodes != null && !nodes.isEmpty()) { - show(nodes); - } - } - - /** Show a tooltip listing the lint errors for the given nodes */ - private void show(List<UiViewElementNode> nodes) { - hide(); - - if (!AdtPrefs.getPrefs().isLintOnSave()) { - return; - } - - mTip = new LintTooltip(mCanvas, nodes); - Rectangle rect = mCanvas.getBounds(); - Point size = mTip.computeSize(SWT.DEFAULT, SWT.DEFAULT); - Point pos = mCanvas.toDisplay(rect.x, rect.y + rect.height); - if (size.x > rect.width) { - size = mTip.computeSize(rect.width, SWT.DEFAULT); - } - mTip.setBounds(pos.x, pos.y, size.x, size.y); - - mShowingNodes = nodes; - mTip.setVisible(true); - } - - /** - * Compute the list of nodes which have lint warnings near the given mouse - * coordinates - * - * @param event the mouse cursor event - * @return a list of nodes, possibly empty - */ - @Nullable - private List<UiViewElementNode> computeNodes(Event event) { - LayoutPoint p = ControlPoint.create(mCanvas, event.x, event.y).toLayout(); - LayoutEditorDelegate delegate = mCanvas.getEditorDelegate(); - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - CanvasTransform mHScale = mCanvas.getHorizontalTransform(); - CanvasTransform mVScale = mCanvas.getVerticalTransform(); - - int layoutIconSize = mHScale.inverseScale(ICON_SIZE); - int slop = mVScale.inverseScale(10); // extra space around icon where tip triggers - - Collection<Node> xmlNodes = delegate.getLintNodes(); - if (xmlNodes == null) { - return null; - } - List<UiViewElementNode> nodes = new ArrayList<UiViewElementNode>(); - for (Node xmlNode : xmlNodes) { - CanvasViewInfo v = viewHierarchy.findViewInfoFor(xmlNode); - if (v != null) { - Rectangle b = v.getAbsRect(); - int x2 = b.x + b.width; - int y2 = b.y + b.height; - if (p.x < x2 - layoutIconSize - slop - || p.x > x2 + slop - || p.y < y2 - layoutIconSize - slop - || p.y > y2 + slop) { - continue; - } - - nodes.add(v.getUiViewNode()); - } - } - - return nodes; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java deleted file mode 100644 index 4577f8d12..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * 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.SdkConstants.ANDROID_LAYOUT_RESOURCE_PREFIX; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_LV_FOOTER; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_LV_HEADER; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_LV_ITEM; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator; -import com.android.ide.eclipse.adt.internal.ui.ResourceChooser; -import com.android.resources.ResourceType; - -import org.eclipse.core.resources.IFile; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.Separator; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.widgets.Menu; -import org.w3c.dom.Node; - -/** - * "Preview List Content" context menu which lists available data types and layouts - * the user can choose to view the ListView as. - */ -public class ListViewTypeMenu extends SubmenuAction { - /** Associated canvas */ - private final LayoutCanvas mCanvas; - /** When true, this menu is for a grid rather than a simple list */ - private boolean mGrid; - /** When true, this menu is for a spinner rather than a simple list */ - private boolean mSpinner; - - /** - * Creates a "Preview List Content" menu - * - * @param canvas associated canvas - * @param isGrid whether the menu is for a grid rather than a list - * @param isSpinner whether the menu is for a spinner rather than a list - */ - public ListViewTypeMenu(LayoutCanvas canvas, boolean isGrid, boolean isSpinner) { - super(isGrid ? "Preview Grid Content" : isSpinner ? "Preview Spinner Layout" - : "Preview List Content"); - mCanvas = canvas; - mGrid = isGrid; - mSpinner = isSpinner; - } - - @Override - protected void addMenuItems(Menu menu) { - GraphicalEditorPart graphicalEditor = mCanvas.getEditorDelegate().getGraphicalEditor(); - if (graphicalEditor.renderingSupports(Capability.ADAPTER_BINDING)) { - IAction action = new PickLayoutAction("Choose Layout...", KEY_LV_ITEM); - new ActionContributionItem(action).fill(menu, -1); - new Separator().fill(menu, -1); - - String selected = getSelectedLayout(); - if (selected != null) { - if (selected.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX)) { - selected = selected.substring(ANDROID_LAYOUT_RESOURCE_PREFIX.length()); - } - } - - if (mSpinner) { - action = new SetListTypeAction("Spinner Item", - "simple_spinner_item", selected); //$NON-NLS-1$ - new ActionContributionItem(action).fill(menu, -1); - action = new SetListTypeAction("Spinner Dropdown Item", - "simple_spinner_dropdown_item", selected); //$NON-NLS-1$ - new ActionContributionItem(action).fill(menu, -1); - return; - } - - action = new SetListTypeAction("Simple List Item", - "simple_list_item_1", selected); //$NON-NLS-1$ - new ActionContributionItem(action).fill(menu, -1); - action = new SetListTypeAction("Simple 2-Line List Item", - "simple_list_item_2", //$NON-NLS-1$ - selected); - new ActionContributionItem(action).fill(menu, -1); - action = new SetListTypeAction("Checked List Item", - "simple_list_item_checked", //$NON-NLS-1$ - selected); - new ActionContributionItem(action).fill(menu, -1); - action = new SetListTypeAction("Single Choice List Item", - "simple_list_item_single_choice", //$NON-NLS-1$ - selected); - new ActionContributionItem(action).fill(menu, -1); - action = new SetListTypeAction("Multiple Choice List Item", - "simple_list_item_multiple_choice", //$NON-NLS-1$ - selected); - if (!mGrid) { - new Separator().fill(menu, -1); - action = new SetListTypeAction("Simple Expandable List Item", - "simple_expandable_list_item_1", selected); //$NON-NLS-1$ - new ActionContributionItem(action).fill(menu, -1); - action = new SetListTypeAction("Simple 2-Line Expandable List Item", - "simple_expandable_list_item_2", //$NON-NLS-1$ - selected); - new ActionContributionItem(action).fill(menu, -1); - - new Separator().fill(menu, -1); - action = new PickLayoutAction("Choose Header...", KEY_LV_HEADER); - new ActionContributionItem(action).fill(menu, -1); - action = new PickLayoutAction("Choose Footer...", KEY_LV_FOOTER); - new ActionContributionItem(action).fill(menu, -1); - } - } else { - // Should we just hide the menu item instead? - addDisabledMessageItem( - "Not supported for this SDK version; try changing the Render Target"); - } - } - - private class SetListTypeAction extends Action { - private final String mLayout; - - public SetListTypeAction(String title, String layout, String selected) { - super(title, IAction.AS_RADIO_BUTTON); - mLayout = layout; - - if (layout.equals(selected)) { - setChecked(true); - } - } - - @Override - public void run() { - if (isChecked()) { - setNewType(KEY_LV_ITEM, ANDROID_LAYOUT_RESOURCE_PREFIX + mLayout); - } - } - } - - /** - * Action which brings up a resource chooser to choose an arbitrary layout as the - * layout to be previewed in the list. - */ - private class PickLayoutAction extends Action { - private final String mType; - - public PickLayoutAction(String title, String type) { - super(title, IAction.AS_PUSH_BUTTON); - mType = type; - } - - @Override - public void run() { - LayoutEditorDelegate delegate = mCanvas.getEditorDelegate(); - IFile file = delegate.getEditor().getInputFile(); - GraphicalEditorPart editor = delegate.getGraphicalEditor(); - ResourceChooser dlg = ResourceChooser.create(editor, ResourceType.LAYOUT) - .setInputValidator(CyclicDependencyValidator.create(file)) - .setInitialSize(85, 10) - .setCurrentResource(getSelectedLayout()); - int result = dlg.open(); - if (result == ResourceChooser.CLEAR_RETURN_CODE) { - setNewType(mType, null); - } else if (result == Window.OK) { - String newType = dlg.getCurrentResource(); - setNewType(mType, newType); - } - } - } - - @Nullable - private String getSelectedLayout() { - String layout = null; - SelectionManager selectionManager = mCanvas.getSelectionManager(); - for (SelectionItem item : selectionManager.getSelections()) { - UiViewElementNode node = item.getViewInfo().getUiViewNode(); - if (node != null) { - Node xmlNode = node.getXmlNode(); - layout = LayoutMetadata.getProperty(xmlNode, KEY_LV_ITEM); - if (layout != null) { - return layout; - } - } - } - - return null; - } - - private void setNewType(@NonNull String type, @Nullable String layout) { - LayoutEditorDelegate delegate = mCanvas.getEditorDelegate(); - GraphicalEditorPart graphicalEditor = delegate.getGraphicalEditor(); - SelectionManager selectionManager = mCanvas.getSelectionManager(); - - for (SelectionItem item : selectionManager.getSnapshot()) { - UiViewElementNode node = item.getViewInfo().getUiViewNode(); - if (node != null) { - Node xmlNode = node.getXmlNode(); - LayoutMetadata.setProperty(delegate.getEditor(), xmlNode, type, layout); - } - } - - // Refresh - graphicalEditor.recomputeLayout(); - mCanvas.redraw(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MarqueeGesture.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MarqueeGesture.java deleted file mode 100644 index 4cfd4fe3d..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MarqueeGesture.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2010 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.graphics.Color; -import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Rectangle; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * A {@link MarqueeGesture} is a gesture for swiping out a selection rectangle. - * With a modifier key, items that intersect the rectangle can be toggled - * instead of added to the new selection set. - */ -public class MarqueeGesture extends Gesture { - /** The {@link Overlay} drawn for the marquee. */ - private MarqueeOverlay mOverlay; - - /** The canvas associated with this gesture. */ - private LayoutCanvas mCanvas; - - /** A copy of the initial selection, when we're toggling the marquee. */ - private Collection<CanvasViewInfo> mInitialSelection; - - /** - * Creates a new marquee selection (selection swiping). - * - * @param canvas The canvas where selection is performed. - * @param toggle If true, toggle the membership of contained elements - * instead of adding it. - */ - public MarqueeGesture(LayoutCanvas canvas, boolean toggle) { - mCanvas = canvas; - - if (toggle) { - List<SelectionItem> selection = canvas.getSelectionManager().getSelections(); - mInitialSelection = new ArrayList<CanvasViewInfo>(selection.size()); - for (SelectionItem item : selection) { - mInitialSelection.add(item.getViewInfo()); - } - } else { - mInitialSelection = Collections.emptySet(); - } - } - - @Override - public void update(ControlPoint pos) { - if (mOverlay == null) { - return; - } - - int x = Math.min(pos.x, mStart.x); - int y = Math.min(pos.y, mStart.y); - int w = Math.abs(pos.x - mStart.x); - int h = Math.abs(pos.y - mStart.y); - - mOverlay.updateSize(x, y, w, h); - - // Compute selection overlaps - LayoutPoint topLeft = ControlPoint.create(mCanvas, x, y).toLayout(); - LayoutPoint bottomRight = ControlPoint.create(mCanvas, x + w, y + h).toLayout(); - mCanvas.getSelectionManager().selectWithin(topLeft, bottomRight, mInitialSelection); - } - - @Override - public List<Overlay> createOverlays() { - mOverlay = new MarqueeOverlay(); - return Collections.<Overlay> singletonList(mOverlay); - } - - /** - * An {@link Overlay} for the {@link MarqueeGesture}; paints a selection - * overlay rectangle matching the mouse coordinate delta between gesture - * start and the current position. - */ - private static class MarqueeOverlay extends Overlay { - /** Rectangle border color. */ - private Color mStroke; - - /** Rectangle fill color. */ - private Color mFill; - - /** Current rectangle coordinates (in terms of control coordinates). */ - private Rectangle mRectangle = new Rectangle(0, 0, 0, 0); - - /** Alpha value of the fill. */ - private int mFillAlpha; - - /** Alpha value of the border. */ - private int mStrokeAlpha; - - /** Constructs a new {@link MarqueeOverlay}. */ - public MarqueeOverlay() { - } - - /** - * Updates the size of the marquee rectangle. - * - * @param x The top left corner of the rectangle, x coordinate. - * @param y The top left corner of the rectangle, y coordinate. - * @param w Rectangle width. - * @param h Rectangle height. - */ - public void updateSize(int x, int y, int w, int h) { - mRectangle.x = x; - mRectangle.y = y; - mRectangle.width = w; - mRectangle.height = h; - } - - @Override - public void create(Device device) { - // TODO: Integrate DrawingStyles with this? - mStroke = new Color(device, 255, 255, 255); - mFill = new Color(device, 128, 128, 128); - mFillAlpha = 64; - mStrokeAlpha = 255; - } - - @Override - public void dispose() { - mStroke.dispose(); - mFill.dispose(); - } - - @Override - public void paint(GC gc) { - if (mRectangle.width > 0 && mRectangle.height > 0) { - gc.setLineStyle(SWT.LINE_SOLID); - gc.setLineWidth(1); - gc.setForeground(mStroke); - gc.setBackground(mFill); - gc.setAlpha(mStrokeAlpha); - gc.drawRectangle(mRectangle); - gc.setAlpha(mFillAlpha); - gc.fillRectangle(mRectangle); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java deleted file mode 100644 index 7cf3a647a..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java +++ /dev/null @@ -1,852 +0,0 @@ -/* - * Copyright (C) 2010 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.common.api.DropFeedback; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.Point; -import com.android.ide.common.api.Rect; -import com.android.ide.eclipse.adt.AdtPlugin; -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.RulesEngine; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode.NodeCreationListener; - -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.jface.viewers.TreeSelection; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.dnd.TransferData; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.widgets.Display; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * The Move gesture provides the operation for moving widgets around in the canvas. - */ -public class MoveGesture extends DropGesture { - /** The associated {@link LayoutCanvas}. */ - private LayoutCanvas mCanvas; - - /** Overlay which paints the drag & drop feedback. */ - private MoveOverlay mOverlay; - - private static final boolean DEBUG = false; - - /** - * The top view right under the drag'n'drop cursor. - * This can only be null during a drag'n'drop when there is no view under the cursor - * or after the state was all cleared. - */ - private CanvasViewInfo mCurrentView; - - /** - * The elements currently being dragged. This will always be non-null for a valid - * drag'n'drop that happens within the same instance of Eclipse. - * <p/> - * In the event that the drag and drop happens between different instances of Eclipse - * this will remain null. - */ - private SimpleElement[] mCurrentDragElements; - - /** - * The first view under the cursor that responded to onDropEnter is called the "target view". - * It can differ from mCurrentView, typically because a terminal View doesn't - * accept drag'n'drop so its parent layout became the target drag'n'drop receiver. - * <p/> - * The target node is the proxy node associated with the target view. - * This can be null if no view under the cursor accepted the drag'n'drop or if the node - * factory couldn't create a proxy for it. - */ - private NodeProxy mTargetNode; - - /** - * The latest drop feedback returned by IViewRule.onDropEnter/Move. - */ - private DropFeedback mFeedback; - - /** - * {@link #dragLeave(DropTargetEvent)} is unfortunately called right before data is - * about to be dropped (between the last {@link #dragOver(DropTargetEvent)} and the - * next {@link #dropAccept(DropTargetEvent)}). That means we can't just - * trash the current DropFeedback from the current view rule in dragLeave(). - * Instead we preserve it in mLeaveTargetNode and mLeaveFeedback in case a dropAccept - * happens next. - */ - private NodeProxy mLeaveTargetNode; - - /** - * @see #mLeaveTargetNode - */ - private DropFeedback mLeaveFeedback; - - /** - * @see #mLeaveTargetNode - */ - private CanvasViewInfo mLeaveView; - - /** Singleton used to keep track of drag selection in the same Eclipse instance. */ - private final GlobalCanvasDragInfo mGlobalDragInfo; - - /** - * Constructs a new {@link MoveGesture}, tied to the given canvas. - * - * @param canvas The canvas to associate the {@link MoveGesture} with. - */ - public MoveGesture(LayoutCanvas canvas) { - mCanvas = canvas; - mGlobalDragInfo = GlobalCanvasDragInfo.getInstance(); - } - - @Override - public List<Overlay> createOverlays() { - mOverlay = new MoveOverlay(); - return Collections.<Overlay> singletonList(mOverlay); - } - - @Override - public void begin(ControlPoint pos, int startMask) { - super.begin(pos, startMask); - - // Hide selection overlays during a move drag - mCanvas.getSelectionOverlay().setHidden(true); - } - - @Override - public void end(ControlPoint pos, boolean canceled) { - super.end(pos, canceled); - - mCanvas.getSelectionOverlay().setHidden(false); - - // Ensure that the outline is back to showing the current selection, since during - // a drag gesture we temporarily set it to show the current target node instead. - mCanvas.getSelectionManager().syncOutlineSelection(); - } - - /* TODO: Pass modifier mask to drag rules as well! This doesn't work yet since - the drag & drop code seems to steal keyboard events. - @Override - public boolean keyPressed(KeyEvent event) { - update(mCanvas.getGestureManager().getCurrentControlPoint()); - mCanvas.redraw(); - return true; - } - - @Override - public boolean keyReleased(KeyEvent event) { - update(mCanvas.getGestureManager().getCurrentControlPoint()); - mCanvas.redraw(); - return true; - } - */ - - /* - * The cursor has entered the drop target boundaries. - * {@inheritDoc} - */ - @Override - public void dragEnter(DropTargetEvent event) { - if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drag enter", event); - - // Make sure we don't have any residual data from an earlier operation. - clearDropInfo(); - mLeaveTargetNode = null; - mLeaveFeedback = null; - mLeaveView = null; - - // Get the dragged elements. - // - // The current transfered type can be extracted from the event. - // As described in dragOver(), this works basically works on Windows but - // not on Linux or Mac, in which case we can't get the type until we - // receive dropAccept/drop(). - // For consistency we try to use the GlobalCanvasDragInfo instance first, - // and if it fails we use the event transfer type as a backup (but as said - // before it will most likely work only on Windows.) - // In any case this can be null even for a valid transfer. - - mCurrentDragElements = mGlobalDragInfo.getCurrentElements(); - - if (mCurrentDragElements == null) { - SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance(); - if (sxt.isSupportedType(event.currentDataType)) { - mCurrentDragElements = (SimpleElement[]) sxt.nativeToJava(event.currentDataType); - } - } - - // if there is no data to transfer, invalidate the drag'n'drop. - // The assumption is that the transfer should have at least one element with a - // a non-null non-empty FQCN. Everything else is optional. - if (mCurrentDragElements == null || - mCurrentDragElements.length == 0 || - mCurrentDragElements[0] == null || - mCurrentDragElements[0].getFqcn() == null || - mCurrentDragElements[0].getFqcn().length() == 0) { - event.detail = DND.DROP_NONE; - } - - dragOperationChanged(event); - } - - /* - * The operation being performed has changed (e.g. modifier key). - * {@inheritDoc} - */ - @Override - public void dragOperationChanged(DropTargetEvent event) { - if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drag changed", event); - - checkDataType(event); - recomputeDragType(event); - } - - private void recomputeDragType(DropTargetEvent event) { - if (event.detail == DND.DROP_DEFAULT) { - // Default means we can now choose the default operation, either copy or move. - // If the drag comes from the same canvas we default to move, otherwise we - // default to copy. - - if (mGlobalDragInfo.getSourceCanvas() == mCanvas && - (event.operations & DND.DROP_MOVE) != 0) { - event.detail = DND.DROP_MOVE; - } else if ((event.operations & DND.DROP_COPY) != 0) { - event.detail = DND.DROP_COPY; - } - } - - // We don't support other types than copy and move - if (event.detail != DND.DROP_COPY && event.detail != DND.DROP_MOVE) { - event.detail = DND.DROP_NONE; - } - } - - /* - * The cursor has left the drop target boundaries OR data is about to be dropped. - * {@inheritDoc} - */ - @Override - public void dragLeave(DropTargetEvent event) { - if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drag leave"); - - // dragLeave is unfortunately called right before data is about to be dropped - // (between the last dropMove and the next dropAccept). That means we can't just - // trash the current DropFeedback from the current view rule, we need to preserve - // it in case a dropAccept happens next. - // See the corresponding kludge in dropAccept(). - mLeaveTargetNode = mTargetNode; - mLeaveFeedback = mFeedback; - mLeaveView = mCurrentView; - - clearDropInfo(); - } - - /* - * The cursor is moving over the drop target. - * {@inheritDoc} - */ - @Override - public void dragOver(DropTargetEvent event) { - processDropEvent(event); - } - - /* - * The drop is about to be performed. - * The drop target is given a last chance to change the nature of the drop. - * {@inheritDoc} - */ - @Override - public void dropAccept(DropTargetEvent event) { - if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drop accept"); - - checkDataType(event); - - // If we have a valid target node and it matches the one we saved in - // dragLeave then we restore the DropFeedback that we saved in dragLeave. - if (mLeaveTargetNode != null) { - mTargetNode = mLeaveTargetNode; - mFeedback = mLeaveFeedback; - mCurrentView = mLeaveView; - } - - if (mFeedback != null && mFeedback.invalidTarget) { - // The script said we can't drop here. - event.detail = DND.DROP_NONE; - } - - if (mLeaveTargetNode == null || event.detail == DND.DROP_NONE) { - clearDropInfo(); - } - - mLeaveTargetNode = null; - mLeaveFeedback = null; - mLeaveView = null; - } - - /* - * The data is being dropped. - * {@inheritDoc} - */ - @Override - public void drop(final DropTargetEvent event) { - if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "dropped"); - - SimpleElement[] elements = null; - - SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance(); - - if (sxt.isSupportedType(event.currentDataType)) { - if (event.data instanceof SimpleElement[]) { - elements = (SimpleElement[]) event.data; - } - } - - if (elements == null || elements.length < 1) { - if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "drop missing drop data"); - return; - } - - if (mCurrentDragElements != null && Arrays.equals(elements, mCurrentDragElements)) { - elements = mCurrentDragElements; - } - - if (mTargetNode == null) { - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - if (viewHierarchy.isValid() && viewHierarchy.isEmpty()) { - // There is no target node because the drop happens on an empty document. - // Attempt to create a root node accordingly. - createDocumentRoot(elements); - } else { - if (DEBUG) AdtPlugin.printErrorToConsole("DEBUG", "dropped on null targetNode"); - } - return; - } - - updateDropFeedback(mFeedback, event); - - final SimpleElement[] elementsFinal = elements; - final LayoutPoint canvasPoint = getDropLocation(event).toLayout(); - String label = computeUndoLabel(mTargetNode, elements, event.detail); - - // Create node listener which (during the drop) listens for node additions - // and stores the list of added node such that they can be selected afterwards. - final List<UiElementNode> added = new ArrayList<UiElementNode>(); - // List of "index within parent" for each node - final List<Integer> indices = new ArrayList<Integer>(); - NodeCreationListener listener = new NodeCreationListener() { - @Override - public void nodeCreated(UiElementNode parent, UiElementNode child, int index) { - if (parent == mTargetNode.getNode()) { - added.add(child); - - // Adjust existing indices - for (int i = 0, n = indices.size(); i < n; i++) { - int idx = indices.get(i); - if (idx >= index) { - indices.set(i, idx + 1); - } - } - - indices.add(index); - } - } - - @Override - public void nodeDeleted(UiElementNode parent, UiElementNode child, int previousIndex) { - if (parent == mTargetNode.getNode()) { - // Adjust existing indices - for (int i = 0, n = indices.size(); i < n; i++) { - int idx = indices.get(i); - if (idx >= previousIndex) { - indices.set(i, idx - 1); - } - } - - // Make sure we aren't removing the same nodes that are being added - // No, that can happen when canceling out of a drop handler such as - // when dropping an included layout, then canceling out of the - // resource chooser. - //assert !added.contains(child); - } - } - }; - - try { - UiElementNode.addNodeCreationListener(listener); - mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel(label, new Runnable() { - @Override - public void run() { - InsertType insertType = getInsertType(event, mTargetNode); - mCanvas.getRulesEngine().callOnDropped(mTargetNode, - elementsFinal, - mFeedback, - new Point(canvasPoint.x, canvasPoint.y), - insertType); - mTargetNode.applyPendingChanges(); - // Clean up drag if applicable - if (event.detail == DND.DROP_MOVE) { - GlobalCanvasDragInfo.getInstance().removeSource(); - } - mTargetNode.applyPendingChanges(); - } - }); - } finally { - UiElementNode.removeNodeCreationListener(listener); - } - - final List<INode> nodes = new ArrayList<INode>(); - NodeFactory nodeFactory = mCanvas.getNodeFactory(); - for (UiElementNode uiNode : added) { - if (uiNode instanceof UiViewElementNode) { - NodeProxy node = nodeFactory.create((UiViewElementNode) uiNode); - if (node != null) { - nodes.add(node); - } - } - } - - // Select the newly dropped nodes: - // Find out which nodes were added, and look up their corresponding - // CanvasViewInfos. - final SelectionManager selectionManager = mCanvas.getSelectionManager(); - // Don't use the indices to search for corresponding nodes yet, since a - // render may not have happened yet and we'd rather use an up to date - // view hierarchy than indices to look up the right view infos. - if (!selectionManager.selectDropped(nodes, null /* indices */)) { - // In some scenarios we can't find the actual view infos yet; this - // seems to happen when you drag from one canvas to another (see the - // related comment next to the setFocus() call below). In that case - // defer selection briefly until the view hierarchy etc is up to - // date. - Display.getDefault().asyncExec(new Runnable() { - @Override - public void run() { - selectionManager.selectDropped(nodes, indices); - } - }); - } - - clearDropInfo(); - mCanvas.redraw(); - // Request focus: This is *necessary* when you are dragging from one canvas editor - // to another, because without it, the redraw does not seem to be processed (the change - // is invisible until you click on the target canvas to give it focus). - mCanvas.setFocus(); - } - - /** - * Returns the right {@link InsertType} to use for the given drop target event and the - * given target node - * - * @param event the drop target event - * @param mTargetNode the node targeted by the drop - * @return the {link InsertType} to use for the drop - */ - public static InsertType getInsertType(DropTargetEvent event, NodeProxy mTargetNode) { - GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance(); - if (event.detail == DND.DROP_MOVE) { - SelectionItem[] selection = dragInfo.getCurrentSelection(); - if (selection != null) { - for (SelectionItem item : selection) { - if (item.getNode() != null - && item.getNode().getParent() == mTargetNode) { - return InsertType.MOVE_WITHIN; - } - } - } - - return InsertType.MOVE_INTO; - } else if (dragInfo.getSourceCanvas() != null) { - return InsertType.PASTE; - } else { - return InsertType.CREATE; - } - } - - /** - * Computes a suitable Undo label to use for a drop operation, such as - * "Drop Button in LinearLayout" and "Move Widgets in RelativeLayout". - * - * @param targetNode The target of the drop - * @param elements The dragged widgets - * @param detail The DnD mode, as used in {@link DropTargetEvent#detail}. - * @return A string suitable as an undo-label for the drop event - */ - public static String computeUndoLabel(NodeProxy targetNode, - SimpleElement[] elements, int detail) { - // Decide whether it's a move or a copy; we'll label moves specifically - // as a move and consider everything else a "Drop" - String verb = (detail == DND.DROP_MOVE) ? "Move" : "Drop"; - - // Get the type of widget being dropped/moved, IF there is only one. If - // there is more than one, just reference it as "Widgets". - String object; - if (elements != null && elements.length == 1) { - object = getSimpleName(elements[0].getFqcn()); - } else { - object = "Widgets"; - } - - String where = getSimpleName(targetNode.getFqcn()); - - // When we localize this: $1 is the verb (Move or Drop), $2 is the - // object (such as "Button"), and $3 is the place we are doing it (such - // as "LinearLayout"). - return String.format("%1$s %2$s in %3$s", verb, object, where); - } - - /** - * Returns simple name (basename, following last dot) of a fully qualified - * class name. - * - * @param fqcn The fqcn to reduce - * @return The base name of the fqcn - */ - public static String getSimpleName(String fqcn) { - // Note that the following works even when there is no dot, since - // lastIndexOf will return -1 so we get fcqn.substring(-1+1) = - // fcqn.substring(0) = fqcn - return fqcn.substring(fqcn.lastIndexOf('.') + 1); - } - - /** - * Updates the {@link DropFeedback#isCopy} and {@link DropFeedback#sameCanvas} fields - * of the given {@link DropFeedback}. This is generally called right before invoking - * one of the callOnXyz methods of GRE to refresh the fields. - * - * @param df The current {@link DropFeedback}. - * @param event An optional event to determine if the current operation is copy or move. - */ - private void updateDropFeedback(DropFeedback df, DropTargetEvent event) { - if (event != null) { - df.isCopy = event.detail == DND.DROP_COPY; - } - df.sameCanvas = mCanvas == mGlobalDragInfo.getSourceCanvas(); - df.invalidTarget = false; - df.dipScale = mCanvas.getEditorDelegate().getGraphicalEditor().getDipScale(); - df.modifierMask = mCanvas.getGestureManager().getRuleModifierMask(); - - // Set the drag bounds, after converting it from control coordinates to - // layout coordinates - GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance(); - Rect dragBounds = null; - Rect controlDragBounds = dragInfo.getDragBounds(); - if (controlDragBounds != null) { - CanvasTransform ht = mCanvas.getHorizontalTransform(); - CanvasTransform vt = mCanvas.getVerticalTransform(); - double horizScale = ht.getScale(); - double verticalScale = vt.getScale(); - int x = (int) (controlDragBounds.x / horizScale); - int y = (int) (controlDragBounds.y / verticalScale); - int w = (int) (controlDragBounds.w / horizScale); - int h = (int) (controlDragBounds.h / verticalScale); - dragBounds = new Rect(x, y, w, h); - } - int baseline = dragInfo.getDragBaseline(); - if (baseline != -1) { - df.dragBaseline = baseline; - } - df.dragBounds = dragBounds; - } - - /** - * Verifies that event.currentDataType is of type {@link SimpleXmlTransfer}. - * If not, try to find a valid data type. - * Otherwise set the drop to {@link DND#DROP_NONE} to cancel it. - * - * @return True if the data type is accepted. - */ - private static boolean checkDataType(DropTargetEvent event) { - - SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance(); - - TransferData current = event.currentDataType; - - if (sxt.isSupportedType(current)) { - return true; - } - - // We only support SimpleXmlTransfer and the current data type is not right. - // Let's see if we can find another one. - - for (TransferData td : event.dataTypes) { - if (td != current && sxt.isSupportedType(td)) { - // We like this type better. - event.currentDataType = td; - return true; - } - } - - // We failed to find any good transfer type. - event.detail = DND.DROP_NONE; - return false; - } - - /** - * Returns the mouse location of the drop target event. - * - * @param event the drop target event - * @return a {@link ControlPoint} location corresponding to the top left corner - */ - private ControlPoint getDropLocation(DropTargetEvent event) { - return ControlPoint.create(mCanvas, event); - } - - /** - * Called on both dragEnter and dragMove. - * Generates the onDropEnter/Move/Leave events depending on the currently - * selected target node. - */ - private void processDropEvent(DropTargetEvent event) { - if (!mCanvas.getViewHierarchy().isValid()) { - // We don't allow drop on an invalid layout, even if we have some obsolete - // layout info for it. - event.detail = DND.DROP_NONE; - clearDropInfo(); - return; - } - - LayoutPoint p = getDropLocation(event).toLayout(); - - // Is the mouse currently captured by a DropFeedback.captureArea? - boolean isCaptured = false; - if (mFeedback != null) { - Rect r = mFeedback.captureArea; - isCaptured = r != null && r.contains(p.x, p.y); - } - - // We can't switch views/nodes when the mouse is captured - CanvasViewInfo vi; - if (isCaptured) { - vi = mCurrentView; - } else { - vi = mCanvas.getViewHierarchy().findViewInfoAt(p); - - // When dragging into the canvas, if you are not over any other view, target - // the root element (since it may not "fill" the screen, e.g. if you have a linear - // layout but have layout_height wrap_content, then the layout will only extend - // to cover the children in the layout, not the whole visible screen area, which - // may be surprising - if (vi == null) { - vi = mCanvas.getViewHierarchy().getRoot(); - } - } - - boolean isMove = true; - boolean needRedraw = false; - - if (vi != mCurrentView) { - // Current view has changed. Does that also change the target node? - // Note that either mCurrentView or vi can be null. - - if (vi == null) { - // vi is null but mCurrentView is not, no view is a target anymore - // We don't need onDropMove in this case - isMove = false; - needRedraw = true; - event.detail = DND.DROP_NONE; - clearDropInfo(); // this will call callDropLeave. - - } else { - // vi is a new current view. - // Query GRE for onDropEnter on the ViewInfo hierarchy, starting from the child - // towards its parent, till we find one that returns a non-null drop feedback. - - DropFeedback df = null; - NodeProxy targetNode = null; - - for (CanvasViewInfo targetVi = vi; - targetVi != null && df == null; - targetVi = targetVi.getParent()) { - targetNode = mCanvas.getNodeFactory().create(targetVi); - df = mCanvas.getRulesEngine().callOnDropEnter(targetNode, - targetVi.getViewObject(), mCurrentDragElements); - - if (df != null) { - // We should also dispatch an onDropMove() call to the initial enter - // position, such that the view is notified of the position where - // we are within the node immediately (before we for example attempt - // to draw feedback). This is necessary since most views perform the - // guideline computations in onDropMove (since only onDropMove is handed - // the -position- of the mouse), and we want this computation to happen - // before we ask the view to draw its feedback. - updateDropFeedback(df, event); - df = mCanvas.getRulesEngine().callOnDropMove(targetNode, - mCurrentDragElements, df, new Point(p.x, p.y)); - } - - if (df != null && - event.detail == DND.DROP_MOVE && - mCanvas == mGlobalDragInfo.getSourceCanvas()) { - // You can't move an object into itself in the same canvas. - // E.g. case of moving a layout and the node under the mouse is the - // layout itself: a copy would be ok but not a move operation of the - // layout into himself. - - SelectionItem[] selection = mGlobalDragInfo.getCurrentSelection(); - if (selection != null) { - for (SelectionItem cs : selection) { - if (cs.getViewInfo() == targetVi) { - // The node that responded is one of the selection roots. - // Simply invalidate the drop feedback and move on the - // parent in the ViewInfo chain. - - updateDropFeedback(df, event); - mCanvas.getRulesEngine().callOnDropLeave( - targetNode, mCurrentDragElements, df); - df = null; - targetNode = null; - } - } - } - } - } - - if (df == null) { - // Provide visual feedback that we are refusing the drop - event.detail = DND.DROP_NONE; - clearDropInfo(); - - } else if (targetNode != mTargetNode) { - // We found a new target node for the drag'n'drop. - // Release the previous one, if any. - callDropLeave(); - - // And assign the new one - mTargetNode = targetNode; - mFeedback = df; - - // We don't need onDropMove in this case - isMove = false; - } - } - - mCurrentView = vi; - } - - if (isMove && mTargetNode != null && mFeedback != null) { - // this is a move inside the same view - com.android.ide.common.api.Point p2 = - new com.android.ide.common.api.Point(p.x, p.y); - updateDropFeedback(mFeedback, event); - DropFeedback df = mCanvas.getRulesEngine().callOnDropMove( - mTargetNode, mCurrentDragElements, mFeedback, p2); - mCanvas.getGestureManager().updateMessage(mFeedback); - - if (df == null) { - // The target is no longer interested in the drop move. - event.detail = DND.DROP_NONE; - callDropLeave(); - - } else if (df != mFeedback) { - mFeedback = df; - } - } - - if (mFeedback != null) { - if (event.detail == DND.DROP_NONE && !mFeedback.invalidTarget) { - // If we previously provided visual feedback that we were refusing - // the drop, we now need to change it to mean we're accepting it. - event.detail = DND.DROP_DEFAULT; - recomputeDragType(event); - - } else if (mFeedback.invalidTarget) { - // Provide visual feedback that we are refusing the drop - event.detail = DND.DROP_NONE; - } - } - - if (needRedraw || (mFeedback != null && mFeedback.requestPaint)) { - mCanvas.redraw(); - } - - // Update outline to show the target node there - OutlinePage outline = mCanvas.getOutlinePage(); - TreeSelection newSelection = TreeSelection.EMPTY; - if (mCurrentView != null && mTargetNode != null) { - // Find the view corresponding to the target node. The current view can be a leaf - // view whereas the target node is always a parent layout. - if (mCurrentView.getUiViewNode() != mTargetNode.getNode()) { - mCurrentView = mCurrentView.getParent(); - } - if (mCurrentView != null && mCurrentView.getUiViewNode() == mTargetNode.getNode()) { - TreePath treePath = SelectionManager.getTreePath(mCurrentView); - newSelection = new TreeSelection(treePath); - } - } - - ISelection currentSelection = outline.getSelection(); - if (currentSelection == null || !currentSelection.equals(newSelection)) { - outline.setSelection(newSelection); - } - } - - /** - * Calls onDropLeave on mTargetNode with the current mFeedback. <br/> - * Then clears mTargetNode and mFeedback. - */ - private void callDropLeave() { - if (mTargetNode != null && mFeedback != null) { - updateDropFeedback(mFeedback, null); - mCanvas.getRulesEngine().callOnDropLeave(mTargetNode, mCurrentDragElements, mFeedback); - } - - mTargetNode = null; - mFeedback = null; - } - - private void clearDropInfo() { - callDropLeave(); - mCurrentView = null; - mCanvas.redraw(); - } - - /** - * Creates a root element in an empty document. - * Only the first element's FQCN of the dragged elements is used. - * <p/> - * Actual XML handling is done by {@link LayoutCanvas#createDocumentRoot(String)}. - */ - private void createDocumentRoot(SimpleElement[] elements) { - if (elements == null || elements.length < 1 || elements[0] == null) { - return; - } - - mCanvas.createDocumentRoot(elements[0]); - } - - /** - * An {@link Overlay} to paint the move feedback. This just delegates to the - * layout rules. - */ - private class MoveOverlay extends Overlay { - @Override - public void paint(GC gc) { - if (mTargetNode != null && mFeedback != null) { - RulesEngine rulesEngine = mCanvas.getRulesEngine(); - rulesEngine.callDropFeedbackPaint(mCanvas.getGcWrapper(), mTargetNode, mFeedback); - mFeedback.requestPaint = false; - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineDragListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineDragListener.java deleted file mode 100644 index 1af3053e3..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineDragListener.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2010 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.jface.viewers.TreeViewer; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DragSourceEvent; -import org.eclipse.swt.dnd.DragSourceListener; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeItem; - -import java.util.ArrayList; - -/** Drag listener for the outline page */ -/* package */ class OutlineDragListener implements DragSourceListener { - private TreeViewer mTreeViewer; - private OutlinePage mOutlinePage; - private final ArrayList<SelectionItem> mDragSelection = new ArrayList<SelectionItem>(); - private SimpleElement[] mDragElements; - - public OutlineDragListener(OutlinePage outlinePage, TreeViewer treeViewer) { - super(); - mOutlinePage = outlinePage; - mTreeViewer = treeViewer; - } - - @Override - public void dragStart(DragSourceEvent e) { - Tree tree = mTreeViewer.getTree(); - - TreeItem overTreeItem = tree.getItem(new Point(e.x, e.y)); - if (overTreeItem == null) { - // Not dragging over a tree item - e.doit = false; - return; - } - CanvasViewInfo over = getViewInfo(overTreeItem); - if (over == null) { - e.doit = false; - return; - } - - // The selection logic for the outline is much simpler than in the canvas, - // because for one thing, the tree selection is updated synchronously on mouse - // down, so it's not possible to start dragging a non-selected item. - // We also don't deliberately disallow root-element dragging since you can - // drag it into another form. - final LayoutCanvas canvas = mOutlinePage.getEditor().getCanvasControl(); - SelectionManager selectionManager = canvas.getSelectionManager(); - TreeItem[] treeSelection = tree.getSelection(); - mDragSelection.clear(); - for (TreeItem item : treeSelection) { - CanvasViewInfo viewInfo = getViewInfo(item); - if (viewInfo != null) { - mDragSelection.add(selectionManager.createSelection(viewInfo)); - } - } - SelectionManager.sanitize(mDragSelection); - - e.doit = !mDragSelection.isEmpty(); - int imageCount = mDragSelection.size(); - if (e.doit) { - mDragElements = SelectionItem.getAsElements(mDragSelection); - GlobalCanvasDragInfo.getInstance().startDrag(mDragElements, - mDragSelection.toArray(new SelectionItem[imageCount]), - canvas, new Runnable() { - @Override - public void run() { - canvas.getClipboardSupport().deleteSelection("Remove", - mDragSelection); - } - }); - return; - } - - e.detail = DND.DROP_NONE; - } - - @Override - public void dragSetData(DragSourceEvent e) { - if (TextTransfer.getInstance().isSupportedType(e.dataType)) { - LayoutCanvas canvas = mOutlinePage.getEditor().getCanvasControl(); - e.data = SelectionItem.getAsText(canvas, mDragSelection); - return; - } - - if (SimpleXmlTransfer.getInstance().isSupportedType(e.dataType)) { - e.data = mDragElements; - return; - } - - // otherwise we failed - e.detail = DND.DROP_NONE; - e.doit = false; - } - - @Override - public void dragFinished(DragSourceEvent e) { - // Unregister the dragged data. - // Clear the selection - mDragSelection.clear(); - mDragElements = null; - GlobalCanvasDragInfo.getInstance().stopDrag(); - } - - private CanvasViewInfo getViewInfo(TreeItem item) { - Object data = item.getData(); - if (data != null) { - return OutlinePage.getViewInfo(data); - } - - return null; - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineDropListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineDropListener.java deleted file mode 100644 index f4a826fa2..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineDropListener.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2010 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.common.api.INode; -import com.android.ide.common.api.InsertType; -import com.android.ide.common.layout.BaseLayoutRule; -import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; - -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.ViewerDropAdapter; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.dnd.TransferData; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** Drop listener for the outline page */ -/*package*/ class OutlineDropListener extends ViewerDropAdapter { - private final OutlinePage mOutlinePage; - - public OutlineDropListener(OutlinePage outlinePage, TreeViewer treeViewer) { - super(treeViewer); - mOutlinePage = outlinePage; - } - - @Override - public void dragEnter(DropTargetEvent event) { - if (event.detail == DND.DROP_NONE && GlobalCanvasDragInfo.getInstance().isDragging()) { - // For some inexplicable reason, we get DND.DROP_NONE from the palette - // even though in its drag start we set DND.DROP_COPY, so correct that here... - int operation = DND.DROP_COPY; - event.detail = operation; - } - super.dragEnter(event); - } - - @Override - public boolean performDrop(Object data) { - final DropTargetEvent event = getCurrentEvent(); - if (event == null) { - return false; - } - int location = determineLocation(event); - if (location == LOCATION_NONE) { - return false; - } - - final SimpleElement[] elements; - SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance(); - if (sxt.isSupportedType(event.currentDataType)) { - if (data instanceof SimpleElement[]) { - elements = (SimpleElement[]) data; - } else { - return false; - } - } else { - return false; - } - if (elements.length == 0) { - return false; - } - - // Determine target: - CanvasViewInfo parent = OutlinePage.getViewInfo(event.item.getData()); - if (parent == null) { - return false; - } - - int index = -1; - UiViewElementNode parentNode = parent.getUiViewNode(); - if (location == LOCATION_BEFORE || location == LOCATION_AFTER) { - UiViewElementNode node = parentNode; - parent = parent.getParent(); - if (parent == null) { - return false; - } - parentNode = parent.getUiViewNode(); - - // Determine index - index = 0; - for (UiElementNode child : parentNode.getUiChildren()) { - if (child == node) { - break; - } - index++; - } - if (location == LOCATION_AFTER) { - index++; - } - } - - // Copy into new position. - final LayoutCanvas canvas = mOutlinePage.getEditor().getCanvasControl(); - final NodeProxy targetNode = canvas.getNodeFactory().create(parentNode); - - // Record children of the target right before the drop (such that we can - // find out after the drop which exact children were inserted) - Set<INode> children = new HashSet<INode>(); - for (INode node : targetNode.getChildren()) { - children.add(node); - } - - String label = MoveGesture.computeUndoLabel(targetNode, elements, event.detail); - final int indexFinal = index; - canvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel(label, new Runnable() { - @Override - public void run() { - InsertType insertType = MoveGesture.getInsertType(event, targetNode); - canvas.getRulesEngine().setInsertType(insertType); - - Object sourceCanvas = GlobalCanvasDragInfo.getInstance().getSourceCanvas(); - boolean createNew = event.detail == DND.DROP_COPY || sourceCanvas != canvas; - BaseLayoutRule.insertAt(targetNode, elements, createNew, indexFinal); - targetNode.applyPendingChanges(); - - // Clean up drag if applicable - if (event.detail == DND.DROP_MOVE) { - GlobalCanvasDragInfo.getInstance().removeSource(); - } - } - }); - - // Now find out which nodes were added, and look up their corresponding - // CanvasViewInfos - final List<INode> added = new ArrayList<INode>(); - for (INode node : targetNode.getChildren()) { - if (!children.contains(node)) { - added.add(node); - } - } - // Select the newly dropped nodes - final SelectionManager selectionManager = canvas.getSelectionManager(); - selectionManager.setOutlineSelection(added); - - canvas.redraw(); - - return true; - } - - @Override - public boolean validateDrop(Object target, int operation, - TransferData transferType) { - DropTargetEvent event = getCurrentEvent(); - if (event == null) { - return false; - } - int location = determineLocation(event); - if (location == LOCATION_NONE) { - return false; - } - - SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance(); - if (!sxt.isSupportedType(transferType)) { - return false; - } - - CanvasViewInfo parent = OutlinePage.getViewInfo(event.item.getData()); - if (parent == null) { - return false; - } - - UiViewElementNode parentNode = parent.getUiViewNode(); - - if (location == LOCATION_ON) { - // Targeting the middle of an item means to add it as a new child - // of the given element. This is only allowed on some types of nodes. - if (!DescriptorsUtils.canInsertChildren(parentNode.getDescriptor(), - parent.getViewObject())) { - return false; - } - } - - // Check that the drop target position is not a child or identical to - // one of the dragged items - SelectionItem[] sel = GlobalCanvasDragInfo.getInstance().getCurrentSelection(); - if (sel != null) { - for (SelectionItem item : sel) { - if (isAncestor(item.getViewInfo().getUiViewNode(), parentNode)) { - return false; - } - } - } - - return true; - } - - /** Returns true if the given parent node is an ancestor of the given child node */ - private boolean isAncestor(UiElementNode parent, UiElementNode child) { - while (child != null) { - if (child == parent) { - return true; - } - child = child.getUiParent(); - } - return false; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineOverlay.java deleted file mode 100644 index e63fff7ab..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineOverlay.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2010 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.graphics.Color; -import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Rectangle; - -/** - * The {@link OutlineOverlay} paints an optional outline on top of the layout, - * showing the structure of the individual Android View elements. - */ -public class OutlineOverlay extends Overlay { - /** The {@link ViewHierarchy} this outline visualizes */ - private final ViewHierarchy mViewHierarchy; - - /** Outline color. Must be disposed, it's NOT a system color. */ - private Color mOutlineColor; - - /** Vertical scaling & scrollbar information. */ - private CanvasTransform mVScale; - - /** Horizontal scaling & scrollbar information. */ - private CanvasTransform mHScale; - - /** - * Constructs a new {@link OutlineOverlay} linked to the given view - * hierarchy. - * - * @param viewHierarchy The {@link ViewHierarchy} to render - * @param hScale The {@link CanvasTransform} to use to transfer horizontal layout - * coordinates to screen coordinates - * @param vScale The {@link CanvasTransform} to use to transfer vertical layout - * coordinates to screen coordinates - */ - public OutlineOverlay( - ViewHierarchy viewHierarchy, - CanvasTransform hScale, - CanvasTransform vScale) { - super(); - mViewHierarchy = viewHierarchy; - mHScale = hScale; - mVScale = vScale; - } - - @Override - public void create(Device device) { - mOutlineColor = new Color(device, SwtDrawingStyle.OUTLINE.getStrokeColor()); - } - - @Override - public void dispose() { - if (mOutlineColor != null) { - mOutlineColor.dispose(); - mOutlineColor = null; - } - } - - @Override - public void paint(GC gc) { - CanvasViewInfo lastRoot = mViewHierarchy.getRoot(); - if (lastRoot != null) { - gc.setForeground(mOutlineColor); - gc.setLineStyle(SwtDrawingStyle.OUTLINE.getLineStyle()); - int oldAlpha = gc.getAlpha(); - gc.setAlpha(SwtDrawingStyle.OUTLINE.getStrokeAlpha()); - drawOutline(gc, lastRoot); - gc.setAlpha(oldAlpha); - } - } - - private void drawOutline(GC gc, CanvasViewInfo info) { - Rectangle r = info.getAbsRect(); - - int x = mHScale.translate(r.x); - int y = mVScale.translate(r.y); - int w = mHScale.scale(r.width); - int h = mVScale.scale(r.height); - - // Add +1 to the width and +1 to the height such that when you have a - // series of boxes (in say a LinearLayout), instead of the bottom of one - // box and the top of the next box being -adjacent-, they -overlap-. - // This makes the outline nicer visually since you don't get - // "double thickness" lines for all adjacent boxes. - gc.drawRectangle(x, y, w + 1, h + 1); - - for (CanvasViewInfo vi : info.getChildren()) { - drawOutline(gc, vi); - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java deleted file mode 100644 index 8178c6871..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java +++ /dev/null @@ -1,1439 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_COLUMN_COUNT; -import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN; -import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN_SPAN; -import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN; -import static com.android.SdkConstants.ATTR_ROW_COUNT; -import static com.android.SdkConstants.ATTR_SRC; -import static com.android.SdkConstants.ATTR_TEXT; -import static com.android.SdkConstants.AUTO_URI; -import static com.android.SdkConstants.DRAWABLE_PREFIX; -import static com.android.SdkConstants.GRID_LAYOUT; -import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.URI_PREFIX; -import static org.eclipse.jface.viewers.StyledString.COUNTER_STYLER; -import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER; - -import com.android.SdkConstants; -import com.android.annotations.VisibleForTesting; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.InsertType; -import com.android.ide.common.layout.BaseLayoutRule; -import com.android.ide.common.layout.GridLayoutRule; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; -import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertySheetPage; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.sdk.ProjectState; -import com.android.ide.eclipse.adt.internal.sdk.Sdk; -import com.android.utils.Pair; - -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IProject; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IContributionItem; -import org.eclipse.jface.action.IMenuListener; -import org.eclipse.jface.action.IMenuManager; -import org.eclipse.jface.action.IToolBarManager; -import org.eclipse.jface.action.MenuManager; -import org.eclipse.jface.action.Separator; -import org.eclipse.jface.preference.JFacePreferences; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.IElementComparer; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.ITreeSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StyledCellLabelProvider; -import org.eclipse.jface.viewers.StyledString; -import org.eclipse.jface.viewers.StyledString.Styler; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.jface.viewers.TreeSelection; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerCell; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -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.graphics.Color; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -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.Label; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeItem; -import org.eclipse.ui.IActionBars; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.INullSelectionListener; -import org.eclipse.ui.IWorkbenchPart; -import org.eclipse.ui.actions.ActionFactory; -import org.eclipse.ui.views.contentoutline.ContentOutlinePage; -import org.eclipse.wb.core.controls.SelfOrientingSashForm; -import org.eclipse.wb.internal.core.editor.structure.IPage; -import org.eclipse.wb.internal.core.editor.structure.PageSiteComposite; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * An outline page for the layout canvas view. - * <p/> - * The page is created by {@link LayoutEditorDelegate#delegateGetAdapter(Class)}. This means - * we have *one* instance of the outline page per open canvas editor. - * <p/> - * It sets itself as a listener on the site's selection service in order to be - * notified of the canvas' selection changes. - * The underlying page is also a selection provider (via IContentOutlinePage) - * and as such it will broadcast selection changes to the site's selection service - * (on which both the layout editor part and the property sheet page listen.) - */ -public class OutlinePage extends ContentOutlinePage - implements INullSelectionListener, IPage { - - /** Label which separates outline text from additional attributes like text prefix or url */ - private static final String LABEL_SEPARATOR = " - "; - - /** Max character count in labels, used for truncation */ - private static final int LABEL_MAX_WIDTH = 50; - - /** - * The graphical editor that created this outline. - */ - private final GraphicalEditorPart mGraphicalEditorPart; - - /** - * RootWrapper is a workaround: we can't set the input of the TreeView to its root - * element, so we introduce a fake parent. - */ - private final RootWrapper mRootWrapper = new RootWrapper(); - - /** - * Menu manager for the context menu actions. - * The actions delegate to the current GraphicalEditorPart. - */ - private MenuManager mMenuManager; - - private Composite mControl; - private PropertySheetPage mPropertySheet; - private PageSiteComposite mPropertySheetComposite; - private boolean mShowPropertySheet; - private boolean mShowHeader; - private boolean mIgnoreSelection; - private boolean mActive = true; - - /** Action to Select All in the tree */ - private final Action mTreeSelectAllAction = new Action() { - @Override - public void run() { - getTreeViewer().getTree().selectAll(); - OutlinePage.this.fireSelectionChanged(getSelection()); - } - - @Override - public String getId() { - return ActionFactory.SELECT_ALL.getId(); - } - }; - - /** Action for moving items up in the tree */ - private Action mMoveUpAction = new Action("Move Up\t-", - IconFactory.getInstance().getImageDescriptor("up")) { //$NON-NLS-1$ - - @Override - public String getId() { - return "adt.outline.moveup"; //$NON-NLS-1$ - } - - @Override - public boolean isEnabled() { - return canMove(false); - } - - @Override - public void run() { - move(false); - } - }; - - /** Action for moving items down in the tree */ - private Action mMoveDownAction = new Action("Move Down\t+", - IconFactory.getInstance().getImageDescriptor("down")) { //$NON-NLS-1$ - - @Override - public String getId() { - return "adt.outline.movedown"; //$NON-NLS-1$ - } - - @Override - public boolean isEnabled() { - return canMove(true); - } - - @Override - public void run() { - move(true); - } - }; - - /** - * Creates a new {@link OutlinePage} associated with the given editor - * - * @param graphicalEditorPart the editor associated with this outline - */ - public OutlinePage(GraphicalEditorPart graphicalEditorPart) { - super(); - mGraphicalEditorPart = graphicalEditorPart; - } - - @Override - public Control getControl() { - // We've injected some controls between the root of the outline page - // and the tree control, so return the actual root (a sash form) rather - // than the superclass' implementation which returns the tree. If we don't - // do this, various checks in the outline page which checks that getControl().getParent() - // is the outline window itself will ignore this page. - return mControl; - } - - void setActive(boolean active) { - if (active != mActive) { - mActive = active; - - // Outlines are by default active when they are created; this is intended - // for deactivating a hidden outline and later reactivating it - assert mControl != null; - if (active) { - getSite().getPage().addSelectionListener(this); - setModel(mGraphicalEditorPart.getCanvasControl().getViewHierarchy().getRoot()); - } else { - getSite().getPage().removeSelectionListener(this); - mRootWrapper.setRoot(null); - if (mPropertySheet != null) { - mPropertySheet.selectionChanged(null, TreeSelection.EMPTY); - } - } - } - } - - /** Refresh all the icon state */ - public void refreshIcons() { - TreeViewer treeViewer = getTreeViewer(); - if (treeViewer != null) { - Tree tree = treeViewer.getTree(); - if (tree != null && !tree.isDisposed()) { - treeViewer.refresh(); - } - } - } - - /** - * Set whether the outline should be shown in the header - * - * @param show whether a header should be shown - */ - public void setShowHeader(boolean show) { - mShowHeader = show; - } - - /** - * Set whether the property sheet should be shown within this outline - * - * @param show whether the property sheet should show - */ - public void setShowPropertySheet(boolean show) { - if (show != mShowPropertySheet) { - mShowPropertySheet = show; - if (mControl == null) { - return; - } - - if (show && mPropertySheet == null) { - createPropertySheet(); - } else if (!show) { - mPropertySheetComposite.dispose(); - mPropertySheetComposite = null; - mPropertySheet.dispose(); - mPropertySheet = null; - } - - mControl.layout(); - } - } - - @Override - public void createControl(Composite parent) { - mControl = new SelfOrientingSashForm(parent, SWT.VERTICAL); - - if (mShowHeader) { - PageSiteComposite mOutlineComposite = new PageSiteComposite(mControl, SWT.BORDER); - mOutlineComposite.setTitleText("Outline"); - mOutlineComposite.setTitleImage(IconFactory.getInstance().getIcon("components_view")); - mOutlineComposite.setPage(new IPage() { - @Override - public void createControl(Composite outlineParent) { - createOutline(outlineParent); - } - - @Override - public void dispose() { - } - - @Override - public Control getControl() { - return getTreeViewer().getTree(); - } - - @Override - public void setToolBar(IToolBarManager toolBarManager) { - makeContributions(null, toolBarManager, null); - toolBarManager.update(false); - } - - @Override - public void setFocus() { - getControl().setFocus(); - } - }); - } else { - createOutline(mControl); - } - - if (mShowPropertySheet) { - createPropertySheet(); - } - } - - private void createOutline(Composite parent) { - if (AdtUtils.isEclipse4()) { - // This is a workaround for the focus behavior in Eclipse 4 where - // the framework ends up calling setFocus() on the first widget in the outline - // AFTER a mouse click has been received. Specifically, if the user clicks in - // the embedded property sheet to for example give a Text property editor focus, - // then after the mouse click, the Outline window activation event is processed, - // and this event causes setFocus() to be called first on the PageBookView (which - // ends up calling setFocus on the first control, normally the TreeViewer), and - // then on the Page itself. We're dealing with the page setFocus() in the override - // of that method in the class, such that it does nothing. - // However, we have to also disable the setFocus on the first control in the - // outline page. To deal with that, we create our *own* first control in the - // outline, and make its setFocus() a no-op. We also make it invisible, since we - // don't actually want anything but the tree viewer showing in the outline. - Text text = new Text(parent, SWT.NONE) { - @Override - public boolean setFocus() { - // Focus no-op - return true; - } - - @Override - protected void checkSubclass() { - // Disable the check that prevents subclassing of SWT components - } - }; - text.setVisible(false); - } - - super.createControl(parent); - - TreeViewer tv = getTreeViewer(); - tv.setAutoExpandLevel(2); - tv.setContentProvider(new ContentProvider()); - tv.setLabelProvider(new LabelProvider()); - tv.setInput(mRootWrapper); - tv.expandToLevel(mRootWrapper.getRoot(), 2); - - int supportedOperations = DND.DROP_COPY | DND.DROP_MOVE; - Transfer[] transfers = new Transfer[] { - SimpleXmlTransfer.getInstance() - }; - - tv.addDropSupport(supportedOperations, transfers, new OutlineDropListener(this, tv)); - tv.addDragSupport(supportedOperations, transfers, new OutlineDragListener(this, tv)); - - // The tree viewer will hold CanvasViewInfo instances, however these - // change each time the canvas is reloaded. OTOH layoutlib gives us - // constant UiView keys which we can use to perform tree item comparisons. - tv.setComparer(new IElementComparer() { - @Override - public int hashCode(Object element) { - if (element instanceof CanvasViewInfo) { - UiViewElementNode key = ((CanvasViewInfo) element).getUiViewNode(); - if (key != null) { - return key.hashCode(); - } - } - if (element != null) { - return element.hashCode(); - } - return 0; - } - - @Override - public boolean equals(Object a, Object b) { - if (a instanceof CanvasViewInfo && b instanceof CanvasViewInfo) { - UiViewElementNode keyA = ((CanvasViewInfo) a).getUiViewNode(); - UiViewElementNode keyB = ((CanvasViewInfo) b).getUiViewNode(); - if (keyA != null) { - return keyA.equals(keyB); - } - } - if (a != null) { - return a.equals(b); - } - return false; - } - }); - tv.addDoubleClickListener(new IDoubleClickListener() { - @Override - public void doubleClick(DoubleClickEvent event) { - // This used to open the property view, but now that properties are docked - // let's use it for something else -- such as showing the editor source - /* - // Front properties panel; its selection is already linked - IWorkbenchPage page = getSite().getPage(); - try { - page.showView(IPageLayout.ID_PROP_SHEET, null, IWorkbenchPage.VIEW_ACTIVATE); - } catch (PartInitException e) { - AdtPlugin.log(e, "Could not activate property sheet"); - } - */ - - TreeItem[] selection = getTreeViewer().getTree().getSelection(); - if (selection.length > 0) { - CanvasViewInfo vi = getViewInfo(selection[0].getData()); - if (vi != null) { - LayoutCanvas canvas = mGraphicalEditorPart.getCanvasControl(); - canvas.show(vi); - } - } - } - }); - - setupContextMenu(); - - // Listen to selection changes from the layout editor - getSite().getPage().addSelectionListener(this); - getControl().addDisposeListener(new DisposeListener() { - - @Override - public void widgetDisposed(DisposeEvent e) { - dispose(); - } - }); - - Tree tree = tv.getTree(); - tree.addKeyListener(new KeyListener() { - - @Override - public void keyPressed(KeyEvent e) { - if (e.character == '-') { - if (mMoveUpAction.isEnabled()) { - mMoveUpAction.run(); - } - } else if (e.character == '+') { - if (mMoveDownAction.isEnabled()) { - mMoveDownAction.run(); - } - } - } - - @Override - public void keyReleased(KeyEvent e) { - } - }); - - setupTooltip(); - } - - /** - * This flag is true when the mouse button is being pressed somewhere inside - * the property sheet - */ - private boolean mPressInPropSheet; - - private void createPropertySheet() { - mPropertySheetComposite = new PageSiteComposite(mControl, SWT.BORDER); - mPropertySheetComposite.setTitleText("Properties"); - mPropertySheetComposite.setTitleImage(IconFactory.getInstance().getIcon("properties_view")); - mPropertySheet = new PropertySheetPage(mGraphicalEditorPart); - mPropertySheetComposite.setPage(mPropertySheet); - if (AdtUtils.isEclipse4()) { - mPropertySheet.getControl().addMouseListener(new MouseListener() { - @Override - public void mouseDown(MouseEvent e) { - mPressInPropSheet = true; - } - - @Override - public void mouseUp(MouseEvent e) { - mPressInPropSheet = false; - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - } - }); - } - } - - @Override - public void setFocus() { - // Only call setFocus on the tree viewer if the mouse click isn't in the property - // sheet area - if (!mPressInPropSheet) { - super.setFocus(); - } - } - - @Override - public void dispose() { - mRootWrapper.setRoot(null); - - getSite().getPage().removeSelectionListener(this); - super.dispose(); - if (mPropertySheet != null) { - mPropertySheet.dispose(); - mPropertySheet = null; - } - } - - /** - * Invoked by {@link LayoutCanvas} to set the model (a.k.a. the root view info). - * - * @param rootViewInfo The root of the view info hierarchy. Can be null. - */ - public void setModel(CanvasViewInfo rootViewInfo) { - if (!mActive) { - return; - } - - mRootWrapper.setRoot(rootViewInfo); - - TreeViewer tv = getTreeViewer(); - if (tv != null && !tv.getTree().isDisposed()) { - Object[] expanded = tv.getExpandedElements(); - tv.refresh(); - tv.setExpandedElements(expanded); - // Ensure that the root is expanded - tv.expandToLevel(rootViewInfo, 2); - } - } - - /** - * Returns the current tree viewer selection. Shouldn't be null, - * although it can be {@link TreeSelection#EMPTY}. - */ - @Override - public ISelection getSelection() { - return super.getSelection(); - } - - /** - * Sets the outline selection. - * - * @param selection Only {@link ITreeSelection} will be used, otherwise the - * selection will be cleared (including a null selection). - */ - @Override - public void setSelection(ISelection selection) { - // TreeViewer should be able to deal with a null selection, but let's make it safe - if (selection == null) { - selection = TreeSelection.EMPTY; - } - if (selection.equals(TreeSelection.EMPTY)) { - return; - } - - super.setSelection(selection); - - TreeViewer tv = getTreeViewer(); - if (tv == null || !(selection instanceof ITreeSelection) || selection.isEmpty()) { - return; - } - - // auto-reveal the selection - ITreeSelection treeSel = (ITreeSelection) selection; - for (TreePath p : treeSel.getPaths()) { - tv.expandToLevel(p, 1); - } - } - - @Override - protected void fireSelectionChanged(ISelection selection) { - super.fireSelectionChanged(selection); - if (mPropertySheet != null && !mIgnoreSelection) { - mPropertySheet.selectionChanged(null, selection); - } - } - - /** - * Listens to a workbench selection. - * Only listen on selection coming from {@link LayoutEditorDelegate}, which avoid - * picking up our own selections. - */ - @Override - public void selectionChanged(IWorkbenchPart part, ISelection selection) { - if (mIgnoreSelection) { - return; - } - - if (part instanceof IEditorPart) { - LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor((IEditorPart) part); - if (delegate != null) { - try { - mIgnoreSelection = true; - setSelection(selection); - - if (mPropertySheet != null) { - mPropertySheet.selectionChanged(part, selection); - } - } finally { - mIgnoreSelection = false; - } - } - } - } - - @Override - public void selectionChanged(SelectionChangedEvent event) { - if (!mIgnoreSelection) { - super.selectionChanged(event); - } - } - - // ---- - - /** - * In theory, the root of the model should be the input of the {@link TreeViewer}, - * which would be the root {@link CanvasViewInfo}. - * That means in theory {@link ContentProvider#getElements(Object)} should return - * its own input as the single root node. - * <p/> - * However as described in JFace Bug 9262, this case is not properly handled by - * a {@link TreeViewer} and leads to an infinite recursion in the tree viewer. - * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=9262 - * <p/> - * The solution is to wrap the tree viewer input in a dummy root node that acts - * as a parent. This class does just that. - */ - private static class RootWrapper { - private CanvasViewInfo mRoot; - - public void setRoot(CanvasViewInfo root) { - mRoot = root; - } - - public CanvasViewInfo getRoot() { - return mRoot; - } - } - - /** Return the {@link CanvasViewInfo} associated with the given TreeItem's data field */ - /* package */ static CanvasViewInfo getViewInfo(Object viewData) { - if (viewData instanceof RootWrapper) { - return ((RootWrapper) viewData).getRoot(); - } - if (viewData instanceof CanvasViewInfo) { - return (CanvasViewInfo) viewData; - } - return null; - } - - // --- Content and Label Providers --- - - /** - * Content provider for the Outline model. - * Objects are going to be {@link CanvasViewInfo}. - */ - private static class ContentProvider implements ITreeContentProvider { - - @Override - public Object[] getChildren(Object element) { - if (element instanceof RootWrapper) { - CanvasViewInfo root = ((RootWrapper)element).getRoot(); - if (root != null) { - return new Object[] { root }; - } - } - if (element instanceof CanvasViewInfo) { - List<CanvasViewInfo> children = ((CanvasViewInfo) element).getUniqueChildren(); - if (children != null) { - return children.toArray(); - } - } - return new Object[0]; - } - - @Override - public Object getParent(Object element) { - if (element instanceof CanvasViewInfo) { - return ((CanvasViewInfo) element).getParent(); - } - return null; - } - - @Override - public boolean hasChildren(Object element) { - if (element instanceof CanvasViewInfo) { - List<CanvasViewInfo> children = ((CanvasViewInfo) element).getChildren(); - if (children != null) { - return children.size() > 0; - } - } - return false; - } - - /** - * Returns the root element. - * Semantically, the root element is the single top-level XML element of the XML layout. - */ - @Override - public Object[] getElements(Object inputElement) { - return getChildren(inputElement); - } - - @Override - public void dispose() { - // pass - } - - @Override - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - // pass - } - } - - /** - * Label provider for the Outline model. - * Objects are going to be {@link CanvasViewInfo}. - */ - private class LabelProvider extends StyledCellLabelProvider { - /** - * Returns the element's logo with a fallback on the android logo. - * - * @param element the tree element - * @return the image to be used as a logo - */ - public Image getImage(Object element) { - if (element instanceof CanvasViewInfo) { - element = ((CanvasViewInfo) element).getUiViewNode(); - } - - if (element instanceof UiViewElementNode) { - UiViewElementNode v = (UiViewElementNode) element; - return v.getIcon(); - } - - return AdtPlugin.getAndroidLogo(); - } - - /** - * Uses {@link UiElementNode#getStyledDescription} for the label for this tree item. - */ - @Override - public void update(ViewerCell cell) { - Object element = cell.getElement(); - StyledString styledString = null; - - CanvasViewInfo vi = null; - if (element instanceof CanvasViewInfo) { - vi = (CanvasViewInfo) element; - element = vi.getUiViewNode(); - } - - Image image = getImage(element); - - if (element instanceof UiElementNode) { - UiElementNode node = (UiElementNode) element; - styledString = node.getStyledDescription(); - Node xmlNode = node.getXmlNode(); - if (xmlNode instanceof Element) { - Element e = (Element) xmlNode; - - // Temporary diagnostics code when developing GridLayout - if (GridLayoutRule.sDebugGridLayout) { - - String namespace; - if (e.getNodeName().equals(GRID_LAYOUT) || - e.getParentNode() != null - && e.getParentNode().getNodeName().equals(GRID_LAYOUT)) { - namespace = ANDROID_URI; - } else { - // Else: probably a v7 gridlayout - IProject project = mGraphicalEditorPart.getProject(); - ProjectState projectState = Sdk.getProjectState(project); - if (projectState != null && projectState.isLibrary()) { - namespace = AUTO_URI; - } else { - ManifestInfo info = ManifestInfo.get(project); - namespace = URI_PREFIX + info.getPackage(); - } - } - - if (e.getNodeName() != null && e.getNodeName().endsWith(GRID_LAYOUT)) { - // Attach rowCount/columnCount info - String rowCount = e.getAttributeNS(namespace, ATTR_ROW_COUNT); - if (rowCount.length() == 0) { - rowCount = "?"; - } - String columnCount = e.getAttributeNS(namespace, ATTR_COLUMN_COUNT); - if (columnCount.length() == 0) { - columnCount = "?"; - } - - styledString.append(" - columnCount=", QUALIFIER_STYLER); - styledString.append(columnCount, QUALIFIER_STYLER); - styledString.append(", rowCount=", QUALIFIER_STYLER); - styledString.append(rowCount, QUALIFIER_STYLER); - } else if (e.getParentNode() != null - && e.getParentNode().getNodeName() != null - && e.getParentNode().getNodeName().endsWith(GRID_LAYOUT)) { - // Attach row/column info - String row = e.getAttributeNS(namespace, ATTR_LAYOUT_ROW); - if (row.length() == 0) { - row = "?"; - } - Styler colStyle = QUALIFIER_STYLER; - String column = e.getAttributeNS(namespace, ATTR_LAYOUT_COLUMN); - if (column.length() == 0) { - column = "?"; - } else { - String colCount = ((Element) e.getParentNode()).getAttributeNS( - namespace, ATTR_COLUMN_COUNT); - if (colCount.length() > 0 && Integer.parseInt(colCount) <= - Integer.parseInt(column)) { - colStyle = StyledString.createColorRegistryStyler( - JFacePreferences.ERROR_COLOR, null); - } - } - String rowSpan = e.getAttributeNS(namespace, ATTR_LAYOUT_ROW_SPAN); - String columnSpan = e.getAttributeNS(namespace, - ATTR_LAYOUT_COLUMN_SPAN); - if (rowSpan.length() == 0) { - rowSpan = "1"; - } - if (columnSpan.length() == 0) { - columnSpan = "1"; - } - - styledString.append(" - cell (row=", QUALIFIER_STYLER); - styledString.append(row, QUALIFIER_STYLER); - styledString.append(',', QUALIFIER_STYLER); - styledString.append("col=", colStyle); - styledString.append(column, colStyle); - styledString.append(')', colStyle); - styledString.append(", span=(", QUALIFIER_STYLER); - styledString.append(columnSpan, QUALIFIER_STYLER); - styledString.append(',', QUALIFIER_STYLER); - styledString.append(rowSpan, QUALIFIER_STYLER); - styledString.append(')', QUALIFIER_STYLER); - - String gravity = e.getAttributeNS(namespace, ATTR_LAYOUT_GRAVITY); - if (gravity != null && gravity.length() > 0) { - styledString.append(" : ", COUNTER_STYLER); - styledString.append(gravity, COUNTER_STYLER); - } - - } - } - - if (e.hasAttributeNS(ANDROID_URI, ATTR_TEXT)) { - // Show the text attribute - String text = e.getAttributeNS(ANDROID_URI, ATTR_TEXT); - if (text != null && text.length() > 0 - && !text.contains(node.getDescriptor().getUiName())) { - if (text.charAt(0) == '@') { - String resolved = mGraphicalEditorPart.findString(text); - if (resolved != null) { - text = resolved; - } - } - if (styledString.length() < LABEL_MAX_WIDTH - LABEL_SEPARATOR.length() - - 2) { - styledString.append(LABEL_SEPARATOR, QUALIFIER_STYLER); - - styledString.append('"', QUALIFIER_STYLER); - styledString.append(truncate(text, styledString), QUALIFIER_STYLER); - styledString.append('"', QUALIFIER_STYLER); - } - } - } else if (e.hasAttributeNS(ANDROID_URI, ATTR_SRC)) { - // Show ImageView source attributes etc - String src = e.getAttributeNS(ANDROID_URI, ATTR_SRC); - if (src != null && src.length() > 0) { - if (src.startsWith(DRAWABLE_PREFIX)) { - src = src.substring(DRAWABLE_PREFIX.length()); - } - styledString.append(LABEL_SEPARATOR, QUALIFIER_STYLER); - styledString.append(truncate(src, styledString), QUALIFIER_STYLER); - } - } else if (e.getTagName().equals(SdkConstants.VIEW_INCLUDE)) { - // Show the include reference. - - // Note: the layout attribute is NOT in the Android namespace - String src = e.getAttribute(SdkConstants.ATTR_LAYOUT); - if (src != null && src.length() > 0) { - if (src.startsWith(LAYOUT_RESOURCE_PREFIX)) { - src = src.substring(LAYOUT_RESOURCE_PREFIX.length()); - } - styledString.append(LABEL_SEPARATOR, QUALIFIER_STYLER); - styledString.append(truncate(src, styledString), QUALIFIER_STYLER); - } - } - } - } else if (element == null && vi != null) { - // It's an inclusion-context: display it - Reference includedWithin = mGraphicalEditorPart.getIncludedWithin(); - if (includedWithin != null) { - styledString = new StyledString(); - styledString.append(includedWithin.getDisplayName(), QUALIFIER_STYLER); - image = IconFactory.getInstance().getIcon(SdkConstants.VIEW_INCLUDE); - } - } - - if (styledString == null) { - styledString = new StyledString(); - styledString.append(element == null ? "(null)" : element.toString()); - } - - cell.setText(styledString.toString()); - cell.setStyleRanges(styledString.getStyleRanges()); - cell.setImage(image); - super.update(cell); - } - - @Override - public boolean isLabelProperty(Object element, String property) { - return super.isLabelProperty(element, property); - } - } - - // --- Context Menu --- - - /** - * This viewer uses its own actions that delegate to the ones given - * by the {@link LayoutCanvas}. All the processing is actually handled - * directly by the canvas and this viewer only gets refreshed as a - * consequence of the canvas changing the XML model. - */ - private void setupContextMenu() { - - mMenuManager = new MenuManager(); - mMenuManager.removeAll(); - - mMenuManager.add(mMoveUpAction); - mMenuManager.add(mMoveDownAction); - mMenuManager.add(new Separator()); - - mMenuManager.add(new SelectionManager.SelectionMenu(mGraphicalEditorPart)); - mMenuManager.add(new Separator()); - final String prefix = LayoutCanvas.PREFIX_CANVAS_ACTION; - mMenuManager.add(new DelegateAction(prefix + ActionFactory.CUT.getId())); - mMenuManager.add(new DelegateAction(prefix + ActionFactory.COPY.getId())); - mMenuManager.add(new DelegateAction(prefix + ActionFactory.PASTE.getId())); - - mMenuManager.add(new Separator()); - - mMenuManager.add(new DelegateAction(prefix + ActionFactory.DELETE.getId())); - - mMenuManager.addMenuListener(new IMenuListener() { - @Override - public void menuAboutToShow(IMenuManager manager) { - // Update all actions to match their LayoutCanvas counterparts - for (IContributionItem contrib : manager.getItems()) { - if (contrib instanceof ActionContributionItem) { - IAction action = ((ActionContributionItem) contrib).getAction(); - if (action instanceof DelegateAction) { - ((DelegateAction) action).updateFromEditorPart(mGraphicalEditorPart); - } - } - } - } - }); - - new DynamicContextMenu( - mGraphicalEditorPart.getEditorDelegate(), - mGraphicalEditorPart.getCanvasControl(), - mMenuManager); - - getTreeViewer().getTree().setMenu(mMenuManager.createContextMenu(getControl())); - - // Update Move Up/Move Down state only when the menu is opened - getTreeViewer().getTree().addMenuDetectListener(new MenuDetectListener() { - @Override - public void menuDetected(MenuDetectEvent e) { - mMenuManager.update(IAction.ENABLED); - } - }); - } - - /** - * An action that delegates its properties and behavior to a target action. - * The target action can be null or it can change overtime, typically as the - * layout canvas' editor part is activated or closed. - */ - private static class DelegateAction extends Action { - private IAction mTargetAction; - private final String mCanvasActionId; - - public DelegateAction(String canvasActionId) { - super(canvasActionId); - setId(canvasActionId); - mCanvasActionId = canvasActionId; - } - - // --- Methods form IAction --- - - /** Returns the target action's {@link #isEnabled()} if defined, or false. */ - @Override - public boolean isEnabled() { - return mTargetAction == null ? false : mTargetAction.isEnabled(); - } - - /** Returns the target action's {@link #isChecked()} if defined, or false. */ - @Override - public boolean isChecked() { - return mTargetAction == null ? false : mTargetAction.isChecked(); - } - - /** Returns the target action's {@link #isHandled()} if defined, or false. */ - @Override - public boolean isHandled() { - return mTargetAction == null ? false : mTargetAction.isHandled(); - } - - /** Runs the target action if defined. */ - @Override - public void run() { - if (mTargetAction != null) { - mTargetAction.run(); - } - super.run(); - } - - /** - * Updates this action to delegate to its counterpart in the given editor part - * - * @param editorPart The editor being updated - */ - public void updateFromEditorPart(GraphicalEditorPart editorPart) { - LayoutCanvas canvas = editorPart == null ? null : editorPart.getCanvasControl(); - if (canvas == null) { - mTargetAction = null; - } else { - mTargetAction = canvas.getAction(mCanvasActionId); - } - - if (mTargetAction != null) { - setText(mTargetAction.getText()); - setId(mTargetAction.getId()); - setDescription(mTargetAction.getDescription()); - setImageDescriptor(mTargetAction.getImageDescriptor()); - setHoverImageDescriptor(mTargetAction.getHoverImageDescriptor()); - setDisabledImageDescriptor(mTargetAction.getDisabledImageDescriptor()); - setToolTipText(mTargetAction.getToolTipText()); - setActionDefinitionId(mTargetAction.getActionDefinitionId()); - setHelpListener(mTargetAction.getHelpListener()); - setAccelerator(mTargetAction.getAccelerator()); - setChecked(mTargetAction.isChecked()); - setEnabled(mTargetAction.isEnabled()); - } else { - setEnabled(false); - } - } - } - - /** Returns the associated editor with this outline */ - /* package */GraphicalEditorPart getEditor() { - return mGraphicalEditorPart; - } - - @Override - public void setActionBars(IActionBars actionBars) { - super.setActionBars(actionBars); - - // Map Outline actions to canvas actions such that they share Undo context etc - LayoutCanvas canvas = mGraphicalEditorPart.getCanvasControl(); - canvas.updateGlobalActions(actionBars); - - // Special handling for Select All since it's different than the canvas (will - // include selecting the root etc) - actionBars.setGlobalActionHandler(mTreeSelectAllAction.getId(), mTreeSelectAllAction); - actionBars.updateActionBars(); - } - - // ---- Move Up/Down Support ---- - - /** Returns true if the current selected item can be moved */ - private boolean canMove(boolean forward) { - CanvasViewInfo viewInfo = getSingleSelectedItem(); - if (viewInfo != null) { - UiViewElementNode node = viewInfo.getUiViewNode(); - if (forward) { - return findNext(node) != null; - } else { - return findPrevious(node) != null; - } - } - - return false; - } - - /** Moves the current selected item down (forward) or up (not forward) */ - private void move(boolean forward) { - CanvasViewInfo viewInfo = getSingleSelectedItem(); - if (viewInfo != null) { - final Pair<UiViewElementNode, Integer> target; - UiViewElementNode selected = viewInfo.getUiViewNode(); - if (forward) { - target = findNext(selected); - } else { - target = findPrevious(selected); - } - if (target != null) { - final LayoutCanvas canvas = mGraphicalEditorPart.getCanvasControl(); - final SelectionManager selectionManager = canvas.getSelectionManager(); - final ArrayList<SelectionItem> dragSelection = new ArrayList<SelectionItem>(); - dragSelection.add(selectionManager.createSelection(viewInfo)); - SelectionManager.sanitize(dragSelection); - - if (!dragSelection.isEmpty()) { - final SimpleElement[] elements = SelectionItem.getAsElements(dragSelection); - UiViewElementNode parentNode = target.getFirst(); - final NodeProxy targetNode = canvas.getNodeFactory().create(parentNode); - - // Record children of the target right before the drop (such that we - // can find out after the drop which exact children were inserted) - Set<INode> children = new HashSet<INode>(); - for (INode node : targetNode.getChildren()) { - children.add(node); - } - - String label = MoveGesture.computeUndoLabel(targetNode, - elements, DND.DROP_MOVE); - canvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel(label, new Runnable() { - @Override - public void run() { - InsertType insertType = InsertType.MOVE_INTO; - if (dragSelection.get(0).getNode().getParent() == targetNode) { - insertType = InsertType.MOVE_WITHIN; - } - canvas.getRulesEngine().setInsertType(insertType); - int index = target.getSecond(); - BaseLayoutRule.insertAt(targetNode, elements, false, index); - targetNode.applyPendingChanges(); - canvas.getClipboardSupport().deleteSelection("Remove", dragSelection); - } - }); - - // Now find out which nodes were added, and look up their - // corresponding CanvasViewInfos - final List<INode> added = new ArrayList<INode>(); - for (INode node : targetNode.getChildren()) { - if (!children.contains(node)) { - added.add(node); - } - } - - selectionManager.setOutlineSelection(added); - } - } - } - } - - /** - * Returns the {@link CanvasViewInfo} for the currently selected item, or null if - * there are no or multiple selected items - * - * @return the current selected item if there is exactly one item selected - */ - private CanvasViewInfo getSingleSelectedItem() { - TreeItem[] selection = getTreeViewer().getTree().getSelection(); - if (selection.length == 1) { - return getViewInfo(selection[0].getData()); - } - - return null; - } - - - /** Returns the pair [parent,index] of the next node (when iterating forward) */ - @VisibleForTesting - /* package */ static Pair<UiViewElementNode, Integer> findNext(UiViewElementNode node) { - UiElementNode parent = node.getUiParent(); - if (parent == null) { - return null; - } - - UiElementNode next = node.getUiNextSibling(); - if (next != null) { - if (DescriptorsUtils.canInsertChildren(next.getDescriptor(), null)) { - return getFirstPosition(next); - } else { - return getPositionAfter(next); - } - } - - next = parent.getUiNextSibling(); - if (next != null) { - return getPositionBefore(next); - } else { - UiElementNode grandParent = parent.getUiParent(); - if (grandParent != null) { - return getLastPosition(grandParent); - } - } - - return null; - } - - /** Returns the pair [parent,index] of the previous node (when iterating backward) */ - @VisibleForTesting - /* package */ static Pair<UiViewElementNode, Integer> findPrevious(UiViewElementNode node) { - UiElementNode prev = node.getUiPreviousSibling(); - if (prev != null) { - UiElementNode curr = prev; - while (true) { - List<UiElementNode> children = curr.getUiChildren(); - if (children.size() > 0) { - curr = children.get(children.size() - 1); - continue; - } - if (DescriptorsUtils.canInsertChildren(curr.getDescriptor(), null)) { - return getFirstPosition(curr); - } else { - if (curr == prev) { - return getPositionBefore(curr); - } else { - return getPositionAfter(curr); - } - } - } - } - - return getPositionBefore(node.getUiParent()); - } - - /** Returns the pair [parent,index] of the position immediately before the given node */ - private static Pair<UiViewElementNode, Integer> getPositionBefore(UiElementNode node) { - if (node != null) { - UiElementNode parent = node.getUiParent(); - if (parent != null && parent instanceof UiViewElementNode) { - return Pair.of((UiViewElementNode) parent, node.getUiSiblingIndex()); - } - } - - return null; - } - - /** Returns the pair [parent,index] of the position immediately following the given node */ - private static Pair<UiViewElementNode, Integer> getPositionAfter(UiElementNode node) { - if (node != null) { - UiElementNode parent = node.getUiParent(); - if (parent != null && parent instanceof UiViewElementNode) { - return Pair.of((UiViewElementNode) parent, node.getUiSiblingIndex() + 1); - } - } - - return null; - } - - /** Returns the pair [parent,index] of the first position inside the given parent */ - private static Pair<UiViewElementNode, Integer> getFirstPosition(UiElementNode parent) { - if (parent != null && parent instanceof UiViewElementNode) { - return Pair.of((UiViewElementNode) parent, 0); - } - - return null; - } - - /** - * Returns the pair [parent,index] of the last position after the given node's - * children - */ - private static Pair<UiViewElementNode, Integer> getLastPosition(UiElementNode parent) { - if (parent != null && parent instanceof UiViewElementNode) { - return Pair.of((UiViewElementNode) parent, parent.getUiChildren().size()); - } - - return null; - } - - /** - * Truncates the given text such that it will fit into the given {@link StyledString} - * up to a maximum length of {@link #LABEL_MAX_WIDTH}. - * - * @param text the text to truncate - * @param string the existing string to be appended to - * @return the truncated string - */ - private static String truncate(String text, StyledString string) { - int existingLength = string.length(); - - if (text.length() + existingLength > LABEL_MAX_WIDTH) { - int truncatedLength = LABEL_MAX_WIDTH - existingLength - 3; - if (truncatedLength > 0) { - return String.format("%1$s...", text.substring(0, truncatedLength)); - } else { - return ""; //$NON-NLS-1$ - } - } - - return text; - } - - @Override - public void setToolBar(IToolBarManager toolBarManager) { - makeContributions(null, toolBarManager, null); - toolBarManager.update(false); - } - - /** - * Sets up a custom tooltip when hovering over tree items. It currently displays the error - * message for the lint warning associated with each node, if any (and only if the hover - * is over the icon portion). - */ - private void setupTooltip() { - final Tree tree = getTreeViewer().getTree(); - - // This is based on SWT Snippet 125 - final Listener listener = new Listener() { - Shell mTip = null; - Label mLabel = null; - - @Override - public void handleEvent(Event event) { - switch(event.type) { - case SWT.Dispose: - case SWT.KeyDown: - case SWT.MouseExit: - case SWT.MouseDown: - case SWT.MouseMove: - if (mTip != null) { - mTip.dispose(); - mTip = null; - mLabel = null; - } - break; - case SWT.MouseHover: - if (mTip != null) { - mTip.dispose(); - mTip = null; - mLabel = null; - } - - String tooltip = null; - - TreeItem item = tree.getItem(new Point(event.x, event.y)); - if (item != null) { - Rectangle rect = item.getBounds(0); - if (event.x - rect.x > 16) { // 16: Standard width of our outline icons - return; - } - - Object data = item.getData(); - if (data != null && data instanceof CanvasViewInfo) { - LayoutEditorDelegate editor = mGraphicalEditorPart.getEditorDelegate(); - CanvasViewInfo vi = (CanvasViewInfo) data; - IMarker marker = editor.getIssueForNode(vi.getUiViewNode()); - if (marker != null) { - tooltip = marker.getAttribute(IMarker.MESSAGE, null); - } - } - - if (tooltip != null) { - Shell shell = tree.getShell(); - Display display = tree.getDisplay(); - - Color fg = display.getSystemColor(SWT.COLOR_INFO_FOREGROUND); - Color bg = display.getSystemColor(SWT.COLOR_INFO_BACKGROUND); - mTip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); - mTip.setBackground(bg); - FillLayout layout = new FillLayout(); - layout.marginWidth = 1; - layout.marginHeight = 1; - mTip.setLayout(layout); - mLabel = new Label(mTip, SWT.WRAP); - mLabel.setForeground(fg); - mLabel.setBackground(bg); - mLabel.setText(tooltip); - mLabel.addListener(SWT.MouseExit, this); - mLabel.addListener(SWT.MouseDown, this); - - Point pt = tree.toDisplay(rect.x, rect.y + rect.height); - Rectangle displayBounds = display.getBounds(); - // -10: Don't extend -all- the way to the edge of the screen - // which would make it look like it has been cropped - int availableWidth = displayBounds.x + displayBounds.width - pt.x - 10; - if (availableWidth < 80) { - availableWidth = 80; - } - Point size = mTip.computeSize(SWT.DEFAULT, SWT.DEFAULT); - if (size.x > availableWidth) { - size = mTip.computeSize(availableWidth, SWT.DEFAULT); - } - mTip.setBounds(pt.x, pt.y, size.x, size.y); - - mTip.setVisible(true); - } - } - } - } - }; - - tree.addListener(SWT.Dispose, listener); - tree.addListener(SWT.KeyDown, listener); - tree.addListener(SWT.MouseMove, listener); - tree.addListener(SWT.MouseHover, listener); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/Overlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/Overlay.java deleted file mode 100644 index 9b7e0eb18..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/Overlay.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2010 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.graphics.Device; -import org.eclipse.swt.graphics.GC; - -/** - * An Overlay is a set of graphics which can be painted on top of the visual - * editor. Different {@link Gesture}s produce context specific overlays, such as - * swiping rectangles from the {@link MarqueeGesture} and guidelines from the - * {@link MoveGesture}. - */ -public abstract class Overlay { - private Device mDevice; - - /** Whether the hover is hidden */ - private boolean mHiding; - - /** - * Construct the overlay, using the given graphics context for painting. - */ - public Overlay() { - super(); - } - - /** - * Initializes the overlay before the first use, if applicable. This is a - * good place to initialize resources like colors. - * - * @param device The device to allocate resources for; the parameter passed - * to {@link #paint} will correspond to this device. - */ - public void create(Device device) { - mDevice = device; - } - - /** - * Releases resources held by the overlay. Called by the editor when an - * overlay has been removed. - */ - public void dispose() { - } - - /** - * Paints the overlay. - * - * @param gc The SWT {@link GC} object to draw into. - */ - public void paint(GC gc) { - throw new IllegalArgumentException("paint() not implemented, probably done " - + "with specialized paint signature"); - } - - /** Returns the device associated with this overlay */ - public Device getDevice() { - return mDevice; - } - - /** - * Returns whether the overlay is hidden - * - * @return true if the selection overlay is hidden - */ - public boolean isHiding() { - return mHiding; - } - - /** - * Hides the overlay - * - * @param hiding true to hide the overlay, false to unhide it (default) - */ - public void setHiding(boolean hiding) { - mHiding = hiding; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java deleted file mode 100644 index 46168b70f..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java +++ /dev/null @@ -1,1265 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_TEXT; -import static com.android.SdkConstants.VALUE_WRAP_CONTENT; -import static com.android.SdkConstants.XMLNS_ANDROID; -import static com.android.SdkConstants.XMLNS_URI; - -import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.RuleAction.Toggle; -import com.android.ide.common.rendering.LayoutLibrary; -import com.android.ide.common.rendering.api.Capability; -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.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; -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.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser; -import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService; -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.PaletteMetadataDescriptor; -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.layout.uimodel.UiViewElementNode; -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.utils.Pair; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IToolBarManager; -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.DragSource; -import org.eclipse.swt.dnd.DragSourceEvent; -import org.eclipse.swt.dnd.DragSourceListener; -import org.eclipse.swt.dnd.Transfer; -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.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseTrackListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -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.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.ToolBar; -import org.eclipse.swt.widgets.ToolItem; -import org.eclipse.wb.internal.core.editor.structure.IPage; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * A palette control for the {@link GraphicalEditorPart}. - * <p/> - * The palette contains several groups, each with a UI name (e.g. layouts and views) and each - * with a list of element descriptors. - * <p/> - * - * 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 context-sensitive tools items, e.g. selection arrow tool, - * group selection tool, alignment, etc. - */ -public class PaletteControl extends Composite { - - /** - * Wrapper to create a {@link PaletteControl} - */ - static class PalettePage implements IPage { - private final GraphicalEditorPart mEditorPart; - private PaletteControl mControl; - - PalettePage(GraphicalEditorPart editor) { - mEditorPart = editor; - } - - @Override - public void createControl(Composite parent) { - mControl = new PaletteControl(parent, mEditorPart); - } - - @Override - public Control getControl() { - return mControl; - } - - @Override - public void dispose() { - mControl.dispose(); - } - - @Override - public void setToolBar(IToolBarManager toolBarManager) { - } - - /** - * Add tool bar items to the given toolbar - * - * @param toolbar the toolbar to add items into - */ - void createToolbarItems(final ToolBar toolbar) { - final ToolItem popupMenuItem = new ToolItem(toolbar, SWT.PUSH); - popupMenuItem.setToolTipText("View Menu"); - popupMenuItem.setImage(IconFactory.getInstance().getIcon("view_menu")); - popupMenuItem.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - Rectangle bounds = popupMenuItem.getBounds(); - // Align menu horizontally with the toolbar button and - // vertically with the bottom of the toolbar - Point point = toolbar.toDisplay(bounds.x, bounds.y + bounds.height); - mControl.showMenu(point.x, point.y); - } - }); - } - - @Override - public void setFocus() { - mControl.setFocus(); - } - } - - /** - * The parent grid layout that contains all the {@link Toggle} and - * {@link IconTextItem} widgets. - */ - private GraphicalEditorPart mEditor; - private Color mBackground; - private Color mForeground; - - /** 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 = null; - /** 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; - private AndroidTargetData mCurrentTargetData; - - /** - * Create the composite. - * @param parent The parent composite. - * @param editor An editor associated with this palette. - */ - public PaletteControl(Composite parent, GraphicalEditorPart editor) { - super(parent, SWT.NONE); - - mEditor = editor; - } - - /** 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]; - } - mAlphabetical = paletteModes.contains(VALUE_ALPHABETICAL); - mCategories = !paletteModes.contains(VALUE_NO_CATEGORIES); - mAutoClose = !paletteModes.contains(VALUE_NO_AUTOCLOSE); - } else { - mPaletteMode = PaletteMode.SMALL_PREVIEW; - } - } - - /** - * 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); - } - - /** 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()); - } - - private void refreshPalette() { - IAndroidTarget oldTarget = mCurrentTarget; - mCurrentTarget = null; - mCurrentTargetData = null; - mCurrentTheme = null; - mCurrentDevice = null; - reloadPalette(oldTarget); - } - - @Override - protected void checkSubclass() { - // Disable the check that prevents subclassing of SWT components - } - - @Override - public void dispose() { - if (mBackground != null) { - mBackground.dispose(); - mBackground = null; - } - if (mForeground != null) { - mForeground.dispose(); - mForeground = 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; - } - - /** Returns true if previews in the palette should be made available */ - private boolean previewsAvailable() { - // Not layoutlib 5 -- we require custom background support to do - // a decent job with previews - LayoutLibrary layoutLibrary = mEditor.getLayoutLibrary(); - return layoutLibrary != null && layoutLibrary.supports(Capability.CUSTOM_BACKGROUND_COLOR); - } - - /** - * Loads or reloads the palette elements by using the layout and view descriptors from the - * given target data. - * - * @param target The target that has just been loaded - */ - public void reloadPalette(IAndroidTarget target) { - ConfigurationChooser configChooser = mEditor.getConfigurationChooser(); - String theme = configChooser.getThemeName(); - String device = configChooser.getDeviceName(); - if (device == null) { - return; - } - AndroidTargetData targetData = - target != null ? Sdk.getCurrent().getTargetData(target) : null; - if (target == mCurrentTarget && targetData == mCurrentTargetData - && mCurrentTheme != null && mCurrentTheme.equals(theme) - && mCurrentDevice != null && mCurrentDevice.equals(device)) { - return; - } - mCurrentTheme = theme; - mCurrentTarget = target; - mCurrentTargetData = targetData; - mCurrentDevice = device; - mPreviewIconFactory.reset(); - - if (targetData == null) { - return; - } - - Set<String> expandedCategories = null; - if (mAccordion != null) { - expandedCategories = mAccordion.getExpandedCategories(); - // We auto-expand all categories when showing icons-only. When returning to some - // other mode we don't want to retain all categories open. - if (expandedCategories.size() > 3) { - expandedCategories = null; - } - } - - // Erase old content and recreate new - for (Control c : getChildren()) { - c.dispose(); - } - - if (mPaletteMode == null) { - loadPaletteMode(); - assert mPaletteMode != null; - } - - // Ensure that the palette mode is supported on this version of the layout library - if (!previewsAvailable()) { - if (mPaletteMode.isPreview()) { - mPaletteMode = PaletteMode.ICON_TEXT; - } - } - - if (mPaletteMode.isPreview()) { - if (mForeground != null) { - mForeground.dispose(); - mForeground = null; - } - if (mBackground != null) { - mBackground.dispose(); - mBackground = null; - } - RGB background = mPreviewIconFactory.getBackgroundColor(); - if (background != null) { - mBackground = new Color(getDisplay(), background); - } - RGB foreground = mPreviewIconFactory.getForegroundColor(); - if (foreground != null) { - mForeground = new Color(getDisplay(), foreground); - } - } - - List<String> headers = Collections.emptyList(); - final Map<String, List<ViewElementDescriptor>> categoryToItems; - categoryToItems = new HashMap<String, List<ViewElementDescriptor>>(); - headers = new ArrayList<String>(); - 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); - } - - headers.add("Custom & Library Views"); - - // Set the categories to expand the first item if - // (1) we don't have a previously selected category, or - // (2) there's just one category anyway, or - // (3) the set of categories have changed so our previously selected category - // doesn't exist anymore (can happen when you toggle "Show Categories") - if ((expandedCategories == null && headers.size() > 0) || headers.size() == 1 || - (expandedCategories != null && expandedCategories.size() >= 1 - && !headers.contains( - expandedCategories.iterator().next().replace("&&", "&")))) { //$NON-NLS-1$ //$NON-NLS-2$ - // Expand the first category if we don't have a previous selection (e.g. refresh) - expandedCategories = Collections.singleton(headers.get(0)); - } - - boolean wrap = mPaletteMode.getWrap(); - - // Pack icon-only view vertically; others stretch to fill palette region - boolean fillVertical = mPaletteMode != PaletteMode.ICON_ONLY; - - mAccordion = new AccordionControl(this, SWT.NONE, headers, fillVertical, wrap, - expandedCategories) { - @Override - protected Composite createChildContainer(Composite parent, Object header, int style) { - assert categoryToItems != null; - List<ViewElementDescriptor> list = categoryToItems.get(header); - final Composite composite; - if (list == null) { - assert header.equals("Custom & Library Views"); - - Composite wrapper = new Composite(parent, SWT.NONE); - GridLayout gridLayout = new GridLayout(1, false); - gridLayout.marginWidth = gridLayout.marginHeight = 0; - gridLayout.horizontalSpacing = gridLayout.verticalSpacing = 0; - gridLayout.marginBottom = 3; - wrapper.setLayout(gridLayout); - if (mPaletteMode.isPreview() && mBackground != null) { - wrapper.setBackground(mBackground); - } - composite = super.createChildContainer(wrapper, header, style); - if (mPaletteMode.isPreview() && mBackground != null) { - composite.setBackground(mBackground); - } - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); - - Button refreshButton = new Button(wrapper, SWT.PUSH | SWT.FLAT); - refreshButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, - false, false, 1, 1)); - refreshButton.setText("Refresh"); - refreshButton.setImage(IconFactory.getInstance().getIcon("refresh")); //$NON-NLS-1$ - refreshButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - CustomViewFinder finder = CustomViewFinder.get(mEditor.getProject()); - finder.refresh(new ViewFinderListener(composite)); - } - }); - - wrapper.layout(true); - } else { - composite = super.createChildContainer(parent, header, style); - if (mPaletteMode.isPreview() && mBackground != null) { - composite.setBackground(mBackground); - } - } - addMenu(composite); - return composite; - } - @Override - protected void createChildren(Composite parent, Object header) { - assert categoryToItems != null; - List<ViewElementDescriptor> list = categoryToItems.get(header); - if (list == null) { - assert header.equals("Custom & Library Views"); - addCustomItems(parent); - return; - } else { - for (ViewElementDescriptor desc : list) { - createItem(parent, desc); - } - } - } - }; - addMenu(mAccordion); - for (CLabel headerLabel : mAccordion.getHeaderLabels()) { - addMenu(headerLabel); - } - setLayout(new FillLayout()); - - // 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()); - } - - layout(true); - } - - protected void addCustomItems(final Composite parent) { - final CustomViewFinder finder = CustomViewFinder.get(mEditor.getProject()); - Collection<String> allViews = finder.getAllViews(); - if (allViews == null) { // Not yet initialized: trigger an async refresh - finder.refresh(new ViewFinderListener(parent)); - return; - } - - // Remove previous content - for (Control c : parent.getChildren()) { - c.dispose(); - } - - // Add new views - for (final String fqcn : allViews) { - CustomViewDescriptorService service = CustomViewDescriptorService.getInstance(); - ViewElementDescriptor desc = service.getDescriptor(mEditor.getProject(), fqcn); - if (desc == null) { - // The descriptor lookup performs validation steps of the class, and may - // in some cases determine that this is not a view and will return null; - // guard against that. - continue; - } - - Control item = createItem(parent, desc); - - // Add control-click listener on custom view items to you can warp to - // (and double click listener too -- the more discoverable, the better.) - if (item instanceof IconTextItem) { - IconTextItem it = (IconTextItem) item; - it.addMouseListener(new MouseAdapter() { - @Override - public void mouseDoubleClick(MouseEvent e) { - AdtPlugin.openJavaClass(mEditor.getProject(), fqcn); - } - - @Override - public void mouseDown(MouseEvent e) { - if ((e.stateMask & SWT.MOD1) != 0) { - AdtPlugin.openJavaClass(mEditor.getProject(), fqcn); - } - } - }); - } - } - } - - /* package */ GraphicalEditorPart getEditor() { - return mEditor; - } - - 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; - } - ConfigurationChooser chooser = mEditor.getConfigurationChooser(); - int dpi = chooser.getConfiguration().getDensity().getDpiValue(); - while (dpi > 160) { - scale = scale / 2; - dpi = dpi / 2; - } - imageControl.setScale(scale); - } - imageControl.setHoverColor(getDisplay().getSystemColor(SWT.COLOR_WHITE)); - if (mBackground != null) { - 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); - if (mForeground != null) { - item.setForeground(mForeground); - item.setBackground(mBackground); - } - } - break; - } - case ICON_TEXT: { - item = new IconTextItem(parent, desc); - break; - } - case ICON_ONLY: { - item = new ImageControl(parent, SWT.None, desc.getGenericIcon()); - item.setToolTipText(desc.getUiName()); - break; - } - default: - throw new IllegalArgumentException("Not yet implemented"); - } - - final DragSource source = new DragSource(item, DND.DROP_COPY); - source.setTransfer(new Transfer[] { SimpleXmlTransfer.getInstance() }); - source.addDragListener(new DescDragSourceListener(desc)); - item.addDisposeListener(new DisposeListener() { - @Override - public void widgetDisposed(DisposeEvent e) { - source.dispose(); - } - }); - addMenu(item); - - return item; - } - - /** - * An Item widget represents one {@link ElementDescriptor} that can be dropped on the - * GLE2 canvas using drag'n'drop. - */ - private static class IconTextItem extends CLabel implements MouseTrackListener { - - private boolean mMouseIn; - - public IconTextItem(Composite parent, ViewElementDescriptor desc) { - super(parent, SWT.NONE); - mMouseIn = false; - - setText(desc.getUiName()); - setImage(desc.getGenericIcon()); - setToolTipText(desc.getTooltip()); - addMouseTrackListener(this); - } - - @Override - public int getStyle() { - int style = super.getStyle(); - if (mMouseIn) { - style |= SWT.SHADOW_IN; - } - return style; - } - - @Override - public void mouseEnter(MouseEvent e) { - if (!mMouseIn) { - mMouseIn = true; - redraw(); - } - } - - @Override - public void mouseExit(MouseEvent e) { - if (mMouseIn) { - mMouseIn = false; - redraw(); - } - } - - @Override - public void mouseHover(MouseEvent e) { - // pass - } - } - - /** - * A {@link DragSourceListener} that deals with drag'n'drop of - * {@link ElementDescriptor}s. - */ - private class DescDragSourceListener implements DragSourceListener { - private final ViewElementDescriptor mDesc; - private SimpleElement[] mElements; - - public DescDragSourceListener(ViewElementDescriptor desc) { - mDesc = desc; - } - - @Override - public void dragStart(DragSourceEvent e) { - // See if we can find out the bounds of this element from a preview image. - // Preview images are created before the drag source listener is notified - // of the started drag. - Rect bounds = null; - Rect dragBounds = null; - - createDragImage(e); - if (mImage != null && !mIsPlaceholder) { - int width = mImageLayoutBounds.width; - int height = mImageLayoutBounds.height; - assert mImageLayoutBounds.x == 0; - assert mImageLayoutBounds.y == 0; - bounds = new Rect(0, 0, width, height); - double scale = mEditor.getCanvasControl().getScale(); - int scaledWidth = (int) (scale * width); - int scaledHeight = (int) (scale * height); - int x = -scaledWidth / 2; - int y = -scaledHeight / 2; - dragBounds = new Rect(x, y, scaledWidth, scaledHeight); - } - - SimpleElement se = new SimpleElement( - SimpleXmlTransfer.getFqcn(mDesc), - null /* parentFqcn */, - bounds /* bounds */, - null /* parentBounds */); - if (mDesc instanceof PaletteMetadataDescriptor) { - PaletteMetadataDescriptor pm = (PaletteMetadataDescriptor) mDesc; - pm.initializeNew(se); - } - mElements = new SimpleElement[] { se }; - - // Register this as the current dragged data - GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance(); - dragInfo.startDrag( - mElements, - null /* selection */, - null /* canvas */, - null /* removeSource */); - dragInfo.setDragBounds(dragBounds); - dragInfo.setDragBaseline(mBaseline); - - - e.doit = true; - } - - @Override - public void dragSetData(DragSourceEvent e) { - // Provide the data for the drop when requested by the other side. - if (SimpleXmlTransfer.getInstance().isSupportedType(e.dataType)) { - e.data = mElements; - } - } - - @Override - public void dragFinished(DragSourceEvent e) { - // Unregister the dragged data. - GlobalCanvasDragInfo.getInstance().stopDrag(); - mElements = null; - if (mImage != null) { - mImage.dispose(); - mImage = null; - } - } - - // TODO: Figure out the right dimensions to use for rendering. - // We WILL crop this after rendering, but for performance reasons it would be good - // not to make it much larger than necessary since to crop this we rely on - // actually scanning pixels. - - /** - * Width of the rendered preview image (before it is cropped), although the actual - * width may be smaller (since we also take the device screen's size into account) - */ - private static final int MAX_RENDER_HEIGHT = 400; - - /** - * Height of the rendered preview image (before it is cropped), although the - * actual width may be smaller (since we also take the device screen's size into - * account) - */ - private static final int MAX_RENDER_WIDTH = 500; - - /** Amount of alpha to multiply into the image (divided by 256) */ - private static final int IMG_ALPHA = 128; - - /** The image shown during the drag */ - private Image mImage; - /** The non-effect bounds of the drag image */ - private Rectangle mImageLayoutBounds; - private int mBaseline = -1; - - /** - * If true, the image is a preview of the view, and if not it is a "fallback" - * image of some sort, such as a rendering of the palette item itself - */ - private boolean mIsPlaceholder; - - private void createDragImage(DragSourceEvent event) { - mBaseline = -1; - Pair<Image, Rectangle> preview = renderPreview(); - if (preview != null) { - mImage = preview.getFirst(); - mImageLayoutBounds = preview.getSecond(); - } else { - mImage = null; - mImageLayoutBounds = null; - } - - mIsPlaceholder = mImage == null; - if (mIsPlaceholder) { - // Couldn't render preview (or the preview is a blank image, such as for - // example the preview of an empty layout), so instead create a placeholder - // image - // Render the palette item itself as an image - Control control = ((DragSource) event.widget).getControl(); - GC gc = new GC(control); - Point size = control.getSize(); - Display display = getDisplay(); - final Image image = new Image(display, size.x, size.y); - gc.copyArea(image, 0, 0); - gc.dispose(); - - BufferedImage awtImage = SwtUtils.convertToAwt(image); - if (awtImage != null) { - awtImage = ImageUtils.createDropShadow(awtImage, 3 /* shadowSize */, - 0.7f /* shadowAlpha */, 0x000000 /* shadowRgb */); - mImage = SwtUtils.convertToSwt(display, awtImage, true, IMG_ALPHA); - } else { - ImageData data = image.getImageData(); - data.alpha = IMG_ALPHA; - - // Changing the ImageData -after- constructing an image on it - // has no effect, so we have to construct a new image. Luckily these - // are tiny images. - mImage = new Image(display, data); - } - image.dispose(); - } - - event.image = mImage; - - if (!mIsPlaceholder) { - // Shift the drag feedback image up such that it's centered under the - // mouse pointer - double scale = mEditor.getCanvasControl().getScale(); - event.offsetX = (int) (scale * mImageLayoutBounds.width / 2); - event.offsetY = (int) (scale * mImageLayoutBounds.height / 2); - } - } - - /** - * Performs the actual rendering of the descriptor into an image and returns the - * image as well as the layout bounds of the image (not including drop shadow etc) - */ - private Pair<Image, Rectangle> renderPreview() { - ViewMetadataRepository repository = ViewMetadataRepository.get(); - RenderMode renderMode = repository.getRenderMode(mDesc.getFullClassName()); - if (renderMode == RenderMode.SKIP) { - return null; - } - - // Create blank XML document - Document document = DomUtilities.createEmptyDocument(); - - // Insert our target view's XML into it as a node - GraphicalEditorPart editor = getEditor(); - LayoutEditorDelegate layoutEditorDelegate = editor.getEditorDelegate(); - - String viewName = mDesc.getXmlLocalName(); - Element element = document.createElement(viewName); - - // Set up a proper name space - Attr attr = document.createAttributeNS(XMLNS_URI, XMLNS_ANDROID); - attr.setValue(ANDROID_URI); - element.getAttributes().setNamedItemNS(attr); - - element.setAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH, VALUE_WRAP_CONTENT); - element.setAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT, VALUE_WRAP_CONTENT); - - // This doesn't apply to all, but doesn't seem to cause harm and makes for a - // better experience with text-oriented views like buttons and texts - element.setAttributeNS(ANDROID_URI, ATTR_TEXT, - DescriptorsUtils.getBasename(mDesc.getUiName())); - - // Is this a palette variation? - if (mDesc instanceof PaletteMetadataDescriptor) { - PaletteMetadataDescriptor pm = (PaletteMetadataDescriptor) mDesc; - pm.initializeNew(element); - } - - document.appendChild(element); - - // Construct UI model from XML - AndroidTargetData data = layoutEditorDelegate.getEditor().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(layoutEditorDelegate.getEditor()); - model.setUnknownDescriptorProvider(editor.getModel().getUnknownDescriptorProvider()); - model.loadFromXmlNode(document); - - // Call the create-hooks such that we for example insert mandatory - // children into views like the DialerFilter, apply image source attributes - // to ImageButtons, etc. - LayoutCanvas canvas = editor.getCanvasControl(); - NodeFactory nodeFactory = canvas.getNodeFactory(); - UiElementNode parent = model.getUiRoot(); - UiElementNode child = parent.getUiChildren().get(0); - if (child instanceof UiViewElementNode) { - UiViewElementNode childUiNode = (UiViewElementNode) child; - NodeProxy childNode = nodeFactory.create(childUiNode); - - // Applying create hooks as part of palette render should - // not trigger model updates - layoutEditorDelegate.getEditor().setIgnoreXmlUpdate(true); - try { - canvas.getRulesEngine().callCreateHooks(layoutEditorDelegate.getEditor(), - null, childNode, InsertType.CREATE_PREVIEW); - childNode.applyPendingChanges(); - } catch (Throwable t) { - AdtPlugin.log(t, "Failed calling creation hooks for widget %1$s", viewName); - } finally { - layoutEditorDelegate.getEditor().setIgnoreXmlUpdate(false); - } - } - - Integer overrideBgColor = null; - boolean hasTransparency = false; - LayoutLibrary layoutLibrary = editor.getLayoutLibrary(); - if (layoutLibrary != null && - layoutLibrary.supports(Capability.CUSTOM_BACKGROUND_COLOR)) { - // It doesn't matter what the background color is as long as the alpha - // is 0 (fully transparent). We're using red to make it more obvious if - // for some reason the background is painted when it shouldn't be. - overrideBgColor = new Integer(0x00FF0000); - } - - RenderSession session = null; - try { - // Use at most the size of the screen for the preview render. - // This is important since when we fill the size of certain views (like - // a SeekBar), we want it to at most be the width of the screen, and for small - // screens the RENDER_WIDTH was wider. - LayoutLog silentLogger = new LayoutLog(); - - session = RenderService.create(editor) - .setModel(model) - .setMaxRenderSize(MAX_RENDER_WIDTH, MAX_RENDER_HEIGHT) - .setLog(silentLogger) - .setOverrideBgColor(overrideBgColor) - .setDecorations(false) - .createRenderSession(); - } catch (Throwable t) { - // Previews can fail for a variety of reasons -- let's not bug - // the user with it - return null; - } - - if (session != null) { - if (session.getResult().isSuccess()) { - BufferedImage image = session.getImage(); - if (image != null) { - BufferedImage cropped; - Rect initialCrop = null; - ViewInfo viewInfo = null; - - List<ViewInfo> viewInfoList = session.getRootViews(); - - if (viewInfoList != null && viewInfoList.size() > 0) { - viewInfo = viewInfoList.get(0); - mBaseline = viewInfo.getBaseLine(); - } - - if (viewInfo != null) { - int x1 = viewInfo.getLeft(); - int x2 = viewInfo.getRight(); - int y2 = viewInfo.getBottom(); - int y1 = viewInfo.getTop(); - initialCrop = new Rect(x1, y1, x2 - x1, y2 - y1); - } - - if (hasTransparency) { - cropped = ImageUtils.cropBlank(image, initialCrop); - } else { - // Find out what the "background" color is such that we can properly - // crop it out of the image. To do this we pick out a pixel in the - // bottom right unpainted area. Rather than pick the one in the far - // bottom corner, we pick one as close to the bounds of the view as - // possible (but still outside of the bounds), such that we can - // deal with themes like the dialog theme. - int edgeX = image.getWidth() -1; - int edgeY = image.getHeight() -1; - if (viewInfo != null) { - if (viewInfo.getRight() < image.getWidth()-1) { - edgeX = viewInfo.getRight()+1; - } - if (viewInfo.getBottom() < image.getHeight()-1) { - edgeY = viewInfo.getBottom()+1; - } - } - int edgeColor = image.getRGB(edgeX, edgeY); - cropped = ImageUtils.cropColor(image, edgeColor, initialCrop); - } - - if (cropped != null) { - int width = initialCrop != null ? initialCrop.w : cropped.getWidth(); - int height = initialCrop != null ? initialCrop.h : cropped.getHeight(); - boolean needsContrast = hasTransparency - && !ImageUtils.containsDarkPixels(cropped); - cropped = ImageUtils.createDropShadow(cropped, - hasTransparency ? 3 : 5 /* shadowSize */, - !hasTransparency ? 0.6f : needsContrast ? 0.8f : 0.7f/*alpha*/, - 0x000000 /* shadowRgb */); - - double scale = canvas.getScale(); - if (scale != 1L) { - cropped = ImageUtils.scale(cropped, scale, scale); - } - - Display display = getDisplay(); - int alpha = (!hasTransparency || !needsContrast) ? IMG_ALPHA : -1; - Image swtImage = SwtUtils.convertToSwt(display, cropped, true, alpha); - Rectangle imageBounds = new Rectangle(0, 0, width, height); - return Pair.of(swtImage, imageBounds); - } - } - } - - session.dispose(); - } - - return null; - } - - /** - * Utility method to print out the contents of the given XML document. This is - * really useful when working on the preview code above. I'm including all the - * code inside a constant false, which means the compiler will omit all the code, - * but I'd like to leave it in the code base and by doing it this way rather than - * as commented out code the code won't be accidentally broken. - */ - @SuppressWarnings("all") - private void dumpDocument(Document document) { - // Diagnostics: print out the XML that we're about to render - if (false) { // Will be omitted by the compiler - org.apache.xml.serialize.OutputFormat outputFormat = - new org.apache.xml.serialize.OutputFormat( - "XML", "ISO-8859-1", true); //$NON-NLS-1$ //$NON-NLS-2$ - outputFormat.setIndent(2); - outputFormat.setLineWidth(100); - outputFormat.setIndenting(true); - outputFormat.setOmitXMLDeclaration(true); - outputFormat.setOmitDocumentType(true); - StringWriter stringWriter = new StringWriter(); - // Using FQN here to avoid having an import above, which will result - // in a deprecation warning, and there isn't a way to annotate a single - // import element with a SuppressWarnings. - org.apache.xml.serialize.XMLSerializer serializer = - new org.apache.xml.serialize.XMLSerializer(stringWriter, outputFormat); - serializer.setNamespaces(true); - try { - serializer.serialize(document.getDocumentElement()); - System.out.println(stringWriter.toString()); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - /** 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; - final static int REFRESH = 4; - final static int RESET = 5; - - ToggleViewOptionAction(String title, int action, boolean checked) { - super(title, (action == REFRESH || action == RESET) ? IAction.AS_PUSH_BUTTON - : IAction.AS_CHECK_BOX); - mAction = action; - if (checked) { - 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; - case REFRESH: - mPreviewIconFactory.refresh(); - refreshPalette(); - break; - case RESET: - mAlphabetical = false; - mCategories = true; - mAutoClose = true; - mPaletteMode = PaletteMode.SMALL_PREVIEW; - refreshPalette(); - break; - } - savePaletteMode(); - } - } - - private void addMenu(Control control) { - control.addMenuDetectListener(new MenuDetectListener() { - @Override - public void menuDetected(MenuDetectEvent e) { - showMenu(e.x, e.y); - } - }); - } - - private void showMenu(int x, int y) { - MenuManager manager = new MenuManager() { - @Override - public boolean isDynamic() { - return true; - } - }; - boolean previews = previewsAvailable(); - for (PaletteMode mode : PaletteMode.values()) { - if (mode.isPreview() && !previews) { - continue; - } - manager.add(new PaletteModeAction(mode)); - } - if (mPaletteMode.isPreview()) { - manager.add(new Separator()); - manager.add(new ToggleViewOptionAction("Refresh Previews", - ToggleViewOptionAction.REFRESH, - false)); - } - 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)); - manager.add(new Separator()); - manager.add(new ToggleViewOptionAction("Reset", - ToggleViewOptionAction.RESET, - false)); - - Menu menu = manager.createContextMenu(PaletteControl.this); - menu.setLocation(x, y); - menu.setVisible(true); - } - - private final class ViewFinderListener implements CustomViewFinder.Listener { - private final Composite mParent; - - private ViewFinderListener(Composite parent) { - mParent = parent; - } - - @Override - public void viewsUpdated(Collection<String> customViews, - Collection<String> thirdPartyViews) { - addCustomItems(mParent); - mParent.layout(true); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PlayAnimationMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PlayAnimationMenu.java deleted file mode 100644 index 629a42f18..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PlayAnimationMenu.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * 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.SdkConstants.FD_RESOURCES; -import static com.android.SdkConstants.FD_RES_ANIMATOR; -import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; - -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.common.rendering.api.IAnimationListener; -import com.android.ide.common.rendering.api.RenderSession; -import com.android.ide.common.rendering.api.Result; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard; -import com.android.resources.ResourceType; -import com.android.utils.Pair; - -import org.eclipse.core.resources.IProject; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.Separator; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.wizard.WizardDialog; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchWindow; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * "Play Animation" context menu which lists available animations in the project and in - * the framework, as well as a "Create Animation" shortcut, and allows the animation to be - * run on the selection - * <p/> - * TODO: Add transport controls for play/rewind/pause/loop, and (if possible) scrubbing - */ -public class PlayAnimationMenu extends SubmenuAction { - /** Associated canvas */ - private final LayoutCanvas mCanvas; - /** Whether this menu is showing local animations or framework animations */ - private boolean mFramework; - - /** - * Creates a "Play Animation" menu - * - * @param canvas associated canvas - */ - public PlayAnimationMenu(LayoutCanvas canvas) { - this(canvas, "Play Animation", false); - } - - /** - * Creates an animation menu; this can be used either for the outer Play animation - * menu, or the inner frameworks-animations list - * - * @param canvas the associated canvas - * @param title menu item name - * @param framework true to show the framework animations, false for the project (and - * nested framework-animation-menu) animations - */ - private PlayAnimationMenu(LayoutCanvas canvas, String title, boolean framework) { - super(title); - mCanvas = canvas; - mFramework = framework; - } - - @Override - protected void addMenuItems(Menu menu) { - SelectionManager selectionManager = mCanvas.getSelectionManager(); - List<SelectionItem> selection = selectionManager.getSelections(); - if (selection.size() != 1) { - addDisabledMessageItem("Select exactly one widget"); - return; - } - - GraphicalEditorPart graphicalEditor = mCanvas.getEditorDelegate().getGraphicalEditor(); - if (graphicalEditor.renderingSupports(Capability.PLAY_ANIMATION)) { - // List of animations - Collection<String> animationNames = graphicalEditor.getResourceNames(mFramework, - ResourceType.ANIMATOR); - if (animationNames.size() > 0) { - // Sort alphabetically - List<String> sortedNames = new ArrayList<String>(animationNames); - Collections.sort(sortedNames); - - for (String animation : sortedNames) { - String title = animation; - IAction action = new PlayAnimationAction(title, animation, mFramework); - new ActionContributionItem(action).fill(menu, -1); - } - - new Separator().fill(menu, -1); - } - - if (!mFramework) { - // Not in the framework submenu: include recent list and create new actions - - // "Create New" action - new ActionContributionItem(new CreateAnimationAction()).fill(menu, -1); - - // Framework resources submenu - new Separator().fill(menu, -1); - PlayAnimationMenu sub = new PlayAnimationMenu(mCanvas, "Android Builtin", true); - new ActionContributionItem(sub).fill(menu, -1); - } - } else { - addDisabledMessageItem( - "Not supported for this SDK version; try changing the Render Target"); - } - } - - private class PlayAnimationAction extends Action { - private final String mAnimationName; - private final boolean mIsFrameworkAnim; - - public PlayAnimationAction(String title, String animationName, boolean isFrameworkAnim) { - super(title, IAction.AS_PUSH_BUTTON); - mAnimationName = animationName; - mIsFrameworkAnim = isFrameworkAnim; - } - - @Override - public void run() { - SelectionManager selectionManager = mCanvas.getSelectionManager(); - List<SelectionItem> selection = selectionManager.getSelections(); - SelectionItem canvasSelection = selection.get(0); - CanvasViewInfo info = canvasSelection.getViewInfo(); - - Object viewObject = info.getViewObject(); - if (viewObject != null) { - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - RenderSession session = viewHierarchy.getSession(); - Result r = session.animate(viewObject, mAnimationName, mIsFrameworkAnim, - new IAnimationListener() { - private boolean mPendingDrawing = false; - - @Override - public void onNewFrame(RenderSession s) { - SelectionOverlay selectionOverlay = mCanvas.getSelectionOverlay(); - if (!selectionOverlay.isHiding()) { - selectionOverlay.setHiding(true); - } - HoverOverlay hoverOverlay = mCanvas.getHoverOverlay(); - if (!hoverOverlay.isHiding()) { - hoverOverlay.setHiding(true); - } - - ImageOverlay imageOverlay = mCanvas.getImageOverlay(); - imageOverlay.setImage(s.getImage(), s.isAlphaChannelImage()); - synchronized (this) { - if (mPendingDrawing == false) { - mCanvas.getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - synchronized (this) { - mPendingDrawing = false; - } - mCanvas.redraw(); - } - }); - mPendingDrawing = true; - } - } - } - - @Override - public boolean isCanceled() { - return false; - } - - @Override - public void done(Result result) { - SelectionOverlay selectionOverlay = mCanvas.getSelectionOverlay(); - selectionOverlay.setHiding(false); - HoverOverlay hoverOverlay = mCanvas.getHoverOverlay(); - hoverOverlay.setHiding(false); - - // Must refresh view hierarchy to force objects back to - // their original positions in case animations have left - // them elsewhere - mCanvas.getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - GraphicalEditorPart graphicalEditor = mCanvas - .getEditorDelegate().getGraphicalEditor(); - graphicalEditor.recomputeLayout(); - } - }); - } - }); - - if (!r.isSuccess()) { - if (r.getErrorMessage() != null) { - AdtPlugin.log(r.getException(), r.getErrorMessage()); - } - } - } - } - } - - /** - * Action which brings up the "Create new XML File" wizard, pre-selected with the - * animation category - */ - private class CreateAnimationAction extends Action { - public CreateAnimationAction() { - super("Create...", IAction.AS_PUSH_BUTTON); - } - - @Override - public void run() { - Shell parent = mCanvas.getShell(); - NewXmlFileWizard wizard = new NewXmlFileWizard(); - LayoutEditorDelegate editor = mCanvas.getEditorDelegate(); - IWorkbenchWindow workbenchWindow = - editor.getEditor().getEditorSite().getWorkbenchWindow(); - IWorkbench workbench = workbenchWindow.getWorkbench(); - String animationDir = FD_RESOURCES + WS_SEP + FD_RES_ANIMATOR; - Pair<IProject, String> pair = Pair.of(editor.getEditor().getProject(), animationDir); - IStructuredSelection selection = new StructuredSelection(pair); - wizard.init(workbench, selection); - WizardDialog dialog = new WizardDialog(parent, wizard); - dialog.create(); - dialog.open(); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java deleted file mode 100644 index 5661b2919..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java +++ /dev/null @@ -1,642 +0,0 @@ -/* - * 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.SdkConstants.DOT_PNG; -import static com.android.SdkConstants.FQCN_DATE_PICKER; -import static com.android.SdkConstants.FQCN_EXPANDABLE_LIST_VIEW; -import static com.android.SdkConstants.FQCN_LIST_VIEW; -import static com.android.SdkConstants.FQCN_TIME_PICKER; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.rendering.LayoutLibrary; -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.common.rendering.api.RenderSession; -import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.common.rendering.api.SessionParams.RenderingMode; -import com.android.ide.common.rendering.api.StyleResourceValue; -import com.android.ide.common.rendering.api.ViewInfo; -import com.android.ide.common.resources.ResourceResolver; -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.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.PaletteMetadataDescriptor; -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.resources.ResourceHelper; -import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; -import com.android.sdklib.IAndroidTarget; -import com.android.utils.Pair; - -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.IStatus; -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.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Properties; - -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 RGB mForeground; - 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; - mForeground = null; - } - - /** - * Deletes all the persistent state for the current settings such that it will be regenerated - */ - public void refresh() { - File imageDir = getImageDir(false); - if (imageDir != null && imageDir.exists()) { - File[] files = imageDir.listFiles(); - for (File file : files) { - file.delete(); - } - imageDir.delete(); - reset(); - } - } - - /** - * 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); - - // Temporary special cases - if (fqn.equals(FQCN_LIST_VIEW) || fqn.equals(FQCN_EXPANDABLE_LIST_VIEW)) { - if (!mPalette.getEditor().renderingSupports(Capability.ADAPTER_BINDING)) { - renderMode = RenderMode.SKIP; - } - } else if (fqn.equals(FQCN_DATE_PICKER) || fqn.equals(FQCN_TIME_PICKER)) { - IAndroidTarget renderingTarget = mPalette.getEditor().getRenderingTarget(); - // In Honeycomb, these widgets only render properly in the Holo themes. - int apiLevel = renderingTarget.getVersion().getApiLevel(); - if (apiLevel == 11) { - String themeName = mPalette.getCurrentTheme(); - if (themeName == null || !themeName.startsWith("Theme.Holo")) { //$NON-NLS-1$ - // Note - it's possible that the the theme is some other theme - // such as a user theme which inherits from Theme.Holo and that - // the render -would- have worked, but it's harder to detect that - // scenario, so we err on the side of caution and just show an - // icon + name for the time widgets. - renderMode = RenderMode.SKIP; - } - } else if (apiLevel >= 12) { - // Currently broken, even for Holo. - renderMode = RenderMode.SKIP; - } // apiLevel <= 10 is fine - } - - 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() { - File imageDir = getImageDir(true); - - GraphicalEditorPart editor = mPalette.getEditor(); - LayoutEditorDelegate layoutEditorDelegate = editor.getEditorDelegate(); - LayoutLibrary layoutLibrary = editor.getLayoutLibrary(); - Integer overrideBgColor = null; - if (layoutLibrary != null) { - if (layoutLibrary.supports(Capability.CUSTOM_BACKGROUND_COLOR)) { - Pair<RGB, RGB> themeColors = getColorsFromTheme(); - RGB bg = themeColors.getFirst(); - RGB fg = themeColors.getSecond(); - if (bg != null) { - storeBackground(imageDir, bg, fg); - overrideBgColor = Integer.valueOf(ImageUtils.rgbToInt(bg, 0xFF)); - } - } - } - - ViewMetadataRepository repository = ViewMetadataRepository.get(); - Document document = repository.getRenderingConfigDoc(); - - if (document == null) { - return false; - } - - // Construct UI model from XML - AndroidTargetData data = layoutEditorDelegate.getEditor().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(layoutEditorDelegate.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; - NodeList childNodes = documentElement.getChildNodes(); - try { - // 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 = childNodes.getLength() == 1 ? 400 : 1600; - - session = RenderService.create(editor) - .setModel(model) - .setOverrideRenderSize(width, height) - .setRenderingMode(RenderingMode.FULL_EXPAND) - .setLog(editor.createRenderLogger("palette")) - .setOverrideBgColor(overrideBgColor) - .setDecorations(false) - .createRenderSession(); - } 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) { - - // Fallback for older platforms where we couldn't do background rendering - // at the beginning of this method - if (mBackground == null) { - Pair<RGB, RGB> themeColors = getColorsFromTheme(); - RGB bg = themeColors.getFirst(); - RGB fg = themeColors.getSecond(); - - if (bg == null) { - // Just use a pixel from the rendering instead. - int p = image.getRGB(image.getWidth() - 1, image.getHeight() - 1); - // However, in this case we don't trust the foreground color - // even if one was found in the themes; pick one that is guaranteed - // to contrast with the background - bg = ImageUtils.intToRgb(p); - if (ImageUtils.getBrightness(ImageUtils.rgbToInt(bg, 255)) < 128) { - fg = new RGB(255, 255, 255); - } else { - fg = new RGB(0, 0, 0); - } - } - storeBackground(imageDir, bg, fg); - assert mBackground != null; - } - - 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); - int parentX = firstRoot.getLeft(); - int parentY = firstRoot.getTop(); - 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 = parentX + info.getLeft(); - int y1 = parentY + info.getTop(); - int x2 = parentX + info.getRight(); - int y2 = parentY + info.getBottom(); - if (x1 != x2 && y1 != y2) { - savePreview(file, image, x1, y1, x2, y2); - } - } - } - } - } else { - StringBuilder sb = new StringBuilder(); - for (int i = 0, n = childNodes.getLength(); i < n; i++) { - Node node = childNodes.item(i); - if (node instanceof Element) { - Element e = (Element) node; - String fqn = repository.getFullClassName(e); - fqn = fqn.substring(fqn.lastIndexOf('.') + 1); - if (sb.length() > 0) { - sb.append(", "); //$NON-NLS-1$ - } - sb.append(fqn); - } - } - AdtPlugin.log(IStatus.WARNING, "Failed to render set of icons for %1$s", - sb.toString()); - - if (session.getResult().getException() != null) { - AdtPlugin.log(session.getResult().getException(), - session.getResult().getErrorMessage()); - } else if (session.getResult().getErrorMessage() != null) { - AdtPlugin.log(IStatus.WARNING, session.getResult().getErrorMessage()); - } - } - - session.dispose(); - } - } - - mPalette.getEditor().recomputeLayout(); - - return true; - } - - /** - * Look up the background and foreground colors from the theme. May not find either - * the background or foreground or both, but will always return a pair of possibly - * null colors. - * - * @return a pair of possibly null color descriptions - */ - @NonNull - private Pair<RGB, RGB> getColorsFromTheme() { - RGB background = null; - RGB foreground = null; - - ResourceResolver resources = mPalette.getEditor().getResourceResolver(); - if (resources == null) { - return Pair.of(background, foreground); - } - StyleResourceValue theme = resources.getCurrentTheme(); - if (theme != null) { - background = resolveThemeColor(resources, "windowBackground"); //$NON-NLS-1$ - if (background == null) { - background = renderDrawableResource("windowBackground"); //$NON-NLS-1$ - // This causes some harm with some themes: We'll find a color, say black, - // that isn't actually rendered in the theme. Better to use null here, - // which will cause the caller to pick a pixel from the observed background - // instead. - //if (background == null) { - // background = resolveThemeColor(resources, "colorBackground"); //$NON-NLS-1$ - //} - } - foreground = resolveThemeColor(resources, "textColorPrimary"); //$NON-NLS-1$ - } - - // Ensure that the foreground color is suitably distinct from the background color - if (background != null) { - int bgRgb = ImageUtils.rgbToInt(background, 0xFF); - int backgroundBrightness = ImageUtils.getBrightness(bgRgb); - if (foreground == null) { - if (backgroundBrightness < 128) { - foreground = new RGB(255, 255, 255); - } else { - foreground = new RGB(0, 0, 0); - } - } else { - int fgRgb = ImageUtils.rgbToInt(foreground, 0xFF); - int foregroundBrightness = ImageUtils.getBrightness(fgRgb); - if (Math.abs(backgroundBrightness - foregroundBrightness) < 64) { - if (backgroundBrightness < 128) { - foreground = new RGB(255, 255, 255); - } else { - foreground = new RGB(0, 0, 0); - } - } - } - } - - return Pair.of(background, foreground); - } - - /** - * Renders the given resource which should refer to a drawable and returns a - * representative color value for the drawable (such as the color in the center) - * - * @param themeItemName the item in the theme to be looked up and rendered - * @return a color representing a typical color in the drawable - */ - private RGB renderDrawableResource(String themeItemName) { - GraphicalEditorPart editor = mPalette.getEditor(); - ResourceResolver resources = editor.getResourceResolver(); - ResourceValue resourceValue = resources.findItemInTheme(themeItemName); - BufferedImage image = RenderService.create(editor) - .setOverrideRenderSize(100, 100) - .renderDrawable(resourceValue); - if (image != null) { - // Use the middle pixel as the color since that works better for gradients; - // solid colors work too. - int rgb = image.getRGB(image.getWidth() / 2, image.getHeight() / 2); - return ImageUtils.intToRgb(rgb); - } - - return null; - } - - private static RGB resolveThemeColor(ResourceResolver resources, String resourceName) { - ResourceValue textColor = resources.findItemInTheme(resourceName); - return ResourceHelper.resolveColor(resources, textColor); - } - - private String getFileName(ElementDescriptor descriptor) { - if (descriptor instanceof PaletteMetadataDescriptor) { - PaletteMetadataDescriptor pmd = (PaletteMetadataDescriptor) descriptor; - StringBuilder sb = new StringBuilder(); - String name = pmd.getUiName(); - // Strip out whitespace, parentheses, etc. - for (int i = 0, n = name.length(); i < n; i++) { - char c = name.charAt(i); - if (Character.isLetter(c)) { - sb.append(c); - } - } - return sb.toString() + DOT_PNG; - } - 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 the name to clean - * @return a cleaned up name - */ - @NonNull - private static String cleanup(@Nullable String name) { - if (name == null) { - return ""; - } - - // 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().hashString(); - 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()); - } - targetName = cleanup(targetName); - themeName = cleanup(themeName); - String deviceName = cleanup(mPalette.getCurrentDevice()); - String dirName = String.format("palette-preview-r16b-%s-%s-%s", targetName, - themeName, deviceName); - 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 bg, RGB fg) { - mBackground = bg; - mForeground = fg; - File file = new File(imageDir, PREVIEW_INFO_FILE); - String colors = String.format( - "background=#%02x%02x%02x\nforeground=#%02x%02x%02x\n", //$NON-NLS-1$ - bg.red, bg.green, bg.blue, - fg.red, fg.green, fg.blue); - AdtPlugin.writeFile(file, colors); - } - - public RGB getBackgroundColor() { - if (mBackground == null) { - initColors(); - } - - return mBackground; - } - - public RGB getForegroundColor() { - if (mForeground == null) { - initColors(); - } - - return mForeground; - } - - public void initColors() { - try { - // Already initialized? Foreground can be null which would call - // initColors again and again, but background is never null after - // initialization so we use it as the have-initialized flag. - if (mBackground != null) { - return; - } - - File imageDir = getImageDir(false); - if (!imageDir.exists()) { - render(); - - // Initialized as part of the render - if (mBackground != null) { - return; - } - } - - File file = new File(imageDir, PREVIEW_INFO_FILE); - if (file.exists()) { - Properties properties = new Properties(); - InputStream is = null; - try { - is = new BufferedInputStream(new FileInputStream(file)); - properties.load(is); - } catch (IOException e) { - AdtPlugin.log(e, "Can't read preview properties"); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - // Nothing useful can be done. - } - } - } - - String colorString = (String) properties.get("background"); //$NON-NLS-1$ - if (colorString != null) { - int rgb = ImageUtils.getColor(colorString.trim()); - mBackground = ImageUtils.intToRgb(rgb); - } - colorString = (String) properties.get("foreground"); //$NON-NLS-1$ - if (colorString != null) { - int rgb = ImageUtils.getColor(colorString.trim()); - mForeground = ImageUtils.intToRgb(rgb); - } - } - - if (mBackground == null) { - mBackground = new RGB(0, 0, 0); - } - // mForeground is allowed to be null. - } catch (Throwable t) { - AdtPlugin.log(t, "Cannot initialize preview color settings"); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java deleted file mode 100644 index 8548830bd..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.rendering.RenderSecurityManager; -import com.android.ide.common.rendering.api.LayoutLog; -import com.android.ide.eclipse.adt.AdtPlugin; - -import org.eclipse.core.runtime.IStatus; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * A {@link LayoutLog} which records the problems it encounters and offers them as a - * single summary at the end - */ -public class RenderLogger extends LayoutLog { - static final String TAG_MISSING_DIMENSION = "missing.dimension"; //$NON-NLS-1$ - - private final String mName; - private List<String> mFidelityWarnings; - private List<String> mWarnings; - private List<String> mErrors; - private boolean mHaveExceptions; - private List<String> mTags; - private List<Throwable> mTraces; - private static Set<String> sIgnoredFidelityWarnings; - private final Object mCredential; - - /** Construct a logger for the given named layout */ - RenderLogger(String name, Object credential) { - mName = name; - mCredential = credential; - } - - /** - * Are there any logged errors or warnings during the render? - * - * @return true if there were problems during the render - */ - public boolean hasProblems() { - return mFidelityWarnings != null || mErrors != null || mWarnings != null || - mHaveExceptions; - } - - /** - * Returns a list of traces encountered during rendering, or null if none - * - * @return a list of traces encountered during rendering, or null if none - */ - @Nullable - public List<Throwable> getFirstTrace() { - return mTraces; - } - - /** - * Returns a (possibly multi-line) description of all the problems - * - * @param includeFidelityWarnings if true, include fidelity warnings in the problem - * summary - * @return a string describing the rendering problems - */ - @NonNull - public String getProblems(boolean includeFidelityWarnings) { - StringBuilder sb = new StringBuilder(); - - if (mErrors != null) { - for (String error : mErrors) { - sb.append(error).append('\n'); - } - } - - if (mWarnings != null) { - for (String warning : mWarnings) { - sb.append(warning).append('\n'); - } - } - - if (includeFidelityWarnings && mFidelityWarnings != null) { - sb.append("The graphics preview in the layout editor may not be accurate:\n"); - for (String warning : mFidelityWarnings) { - sb.append("* "); - sb.append(warning).append('\n'); - } - } - - if (mHaveExceptions) { - sb.append("Exception details are logged in Window > Show View > Error Log"); - } - - return sb.toString(); - } - - /** - * Returns the fidelity warnings - * - * @return the fidelity warnings - */ - @Nullable - public List<String> getFidelityWarnings() { - return mFidelityWarnings; - } - - // ---- extends LayoutLog ---- - - @Override - public void error(String tag, String message, Object data) { - String description = describe(message); - - appendToIdeLog(null, IStatus.ERROR, description); - - // Workaround: older layout libraries don't provide a tag for this error - if (tag == null && message != null - && message.startsWith("Failed to find style ")) { //$NON-NLS-1$ - tag = LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR; - } - - addError(tag, description); - } - - @Override - public void error(String tag, String message, Throwable throwable, Object data) { - String description = describe(message); - appendToIdeLog(throwable, IStatus.ERROR, description); - - if (throwable != null) { - if (throwable instanceof ClassNotFoundException) { - // The project callback is given a chance to resolve classes, - // and when it fails, it will record it in its own list which - // is displayed in a special way (with action hyperlinks etc). - // Therefore, include these messages in the visible render log, - // especially since the user message from a ClassNotFoundException - // is really not helpful (it just lists the class name without - // even mentioning that it is a class-not-found exception.) - return; - } - - if (description.equals(throwable.getLocalizedMessage()) || - description.equals(throwable.getMessage())) { - description = "Exception raised during rendering: " + description; - } - recordThrowable(throwable); - mHaveExceptions = true; - } - - addError(tag, description); - } - - /** - * Record that the given exception was encountered during rendering - * - * @param throwable the exception that was raised - */ - public void recordThrowable(@NonNull Throwable throwable) { - if (mTraces == null) { - mTraces = new ArrayList<Throwable>(); - } - mTraces.add(throwable); - } - - @Override - public void warning(String tag, String message, Object data) { - String description = describe(message); - - boolean log = true; - if (TAG_RESOURCES_FORMAT.equals(tag)) { - if (description.equals("You must supply a layout_width attribute.") //$NON-NLS-1$ - || description.equals("You must supply a layout_height attribute.")) {//$NON-NLS-1$ - tag = TAG_MISSING_DIMENSION; - log = false; - } - } - - if (log) { - appendToIdeLog(null, IStatus.WARNING, description); - } - - addWarning(tag, description); - } - - @Override - public void fidelityWarning(String tag, String message, Throwable throwable, Object data) { - if (sIgnoredFidelityWarnings != null && sIgnoredFidelityWarnings.contains(message)) { - return; - } - - String description = describe(message); - appendToIdeLog(throwable, IStatus.ERROR, description); - - if (throwable != null) { - mHaveExceptions = true; - } - - addFidelityWarning(tag, description); - } - - /** - * Ignore the given render fidelity warning for the current session - * - * @param message the message to be ignored for this session - */ - public static void ignoreFidelityWarning(String message) { - if (sIgnoredFidelityWarnings == null) { - sIgnoredFidelityWarnings = new HashSet<String>(); - } - sIgnoredFidelityWarnings.add(message); - } - - @NonNull - private String describe(@Nullable String message) { - if (message == null) { - return ""; - } else { - return message; - } - } - - private void addWarning(String tag, String description) { - if (mWarnings == null) { - mWarnings = new ArrayList<String>(); - } else if (mWarnings.contains(description)) { - // Avoid duplicates - return; - } - mWarnings.add(description); - addTag(tag); - } - - private void addError(String tag, String description) { - if (mErrors == null) { - mErrors = new ArrayList<String>(); - } else if (mErrors.contains(description)) { - // Avoid duplicates - return; - } - mErrors.add(description); - addTag(tag); - } - - private void addFidelityWarning(String tag, String description) { - if (mFidelityWarnings == null) { - mFidelityWarnings = new ArrayList<String>(); - } else if (mFidelityWarnings.contains(description)) { - // Avoid duplicates - return; - } - mFidelityWarnings.add(description); - addTag(tag); - } - - // ---- Tags ---- - - private void addTag(String tag) { - if (tag != null) { - if (mTags == null) { - mTags = new ArrayList<String>(); - } - mTags.add(tag); - } - } - - /** - * Returns true if the given tag prefix has been seen - * - * @param prefix the tag prefix to look for - * @return true iff any tags with the given prefix was seen during the render - */ - public boolean seenTagPrefix(String prefix) { - if (mTags != null) { - for (String tag : mTags) { - if (tag.startsWith(prefix)) { - return true; - } - } - } - - return false; - } - - /** - * Returns true if the given tag has been seen - * - * @param tag the tag to look for - * @return true iff the tag was seen during the render - */ - public boolean seenTag(String tag) { - if (mTags != null) { - return mTags.contains(tag); - } else { - return false; - } - } - - // Append the given message to the ADT log. Bypass the sandbox if necessary - // such that we can write to the log file. - private void appendToIdeLog(Throwable throwable, int severity, String description) { - boolean token = RenderSecurityManager.enterSafeRegion(mCredential); - try { - if (throwable != null) { - AdtPlugin.log(throwable, "%1$s: %2$s", mName, description); - } else { - AdtPlugin.log(severity, "%1$s: %2$s", mName, description); - } - } finally { - RenderSecurityManager.exitSafeRegion(token); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java deleted file mode 100644 index 5621d5f17..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java +++ /dev/null @@ -1,1333 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX; -import static com.android.SdkConstants.PREFIX_RESOURCE_REF; -import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.MASK_RENDERING; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SMALL_SHADOW_SIZE; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.DEFAULT; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.INCLUDES; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.rendering.api.RenderSession; -import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.common.rendering.api.Result; -import com.android.ide.common.rendering.api.Result.Status; -import com.android.ide.common.resources.ResourceFile; -import com.android.ide.common.resources.ResourceRepository; -import com.android.ide.common.resources.ResourceResolver; -import com.android.ide.common.resources.configuration.FolderConfiguration; -import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Locale; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.NestedConfiguration; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.VaryingConfiguration; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; -import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; -import com.android.ide.eclipse.adt.internal.sdk.Sdk; -import com.android.ide.eclipse.adt.io.IFileWrapper; -import com.android.io.IAbstractFile; -import com.android.resources.Density; -import com.android.resources.ResourceType; -import com.android.resources.ScreenOrientation; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.devices.Device; -import com.android.sdklib.devices.Screen; -import com.android.sdklib.devices.State; -import com.android.utils.SdkUtils; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.jobs.IJobChangeEvent; -import org.eclipse.core.runtime.jobs.IJobChangeListener; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jface.dialogs.InputDialog; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Region; -import org.eclipse.swt.widgets.Display; -import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.progress.UIJob; -import org.w3c.dom.Document; - -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.File; -import java.lang.ref.SoftReference; -import java.util.Comparator; -import java.util.Map; - -/** - * Represents a preview rendering of a given configuration - */ -public class RenderPreview implements IJobChangeListener { - /** Whether previews should use large shadows */ - static final boolean LARGE_SHADOWS = false; - - /** - * Still doesn't work; get exceptions from layoutlib: - * java.lang.IllegalStateException: After scene creation, #init() must be called - * at com.android.layoutlib.bridge.impl.RenderAction.acquire(RenderAction.java:151) - * <p> - * TODO: Investigate. - */ - private static final boolean RENDER_ASYNC = false; - - /** - * Height of the toolbar shown over a preview during hover. Needs to be - * large enough to accommodate icons below. - */ - private static final int HEADER_HEIGHT = 20; - - /** Whether to dump out rendering failures of the previews to the log */ - private static final boolean DUMP_RENDER_DIAGNOSTICS = false; - - /** Extra error checking in debug mode */ - private static final boolean DEBUG = false; - - private static final Image EDIT_ICON; - private static final Image ZOOM_IN_ICON; - private static final Image ZOOM_OUT_ICON; - private static final Image CLOSE_ICON; - private static final int EDIT_ICON_WIDTH; - private static final int ZOOM_IN_ICON_WIDTH; - private static final int ZOOM_OUT_ICON_WIDTH; - private static final int CLOSE_ICON_WIDTH; - static { - ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); - IconFactory icons = IconFactory.getInstance(); - CLOSE_ICON = sharedImages.getImage(ISharedImages.IMG_ETOOL_DELETE); - EDIT_ICON = icons.getIcon("editPreview"); //$NON-NLS-1$ - ZOOM_IN_ICON = icons.getIcon("zoomplus"); //$NON-NLS-1$ - ZOOM_OUT_ICON = icons.getIcon("zoomminus"); //$NON-NLS-1$ - CLOSE_ICON_WIDTH = CLOSE_ICON.getImageData().width; - EDIT_ICON_WIDTH = EDIT_ICON.getImageData().width; - ZOOM_IN_ICON_WIDTH = ZOOM_IN_ICON.getImageData().width; - ZOOM_OUT_ICON_WIDTH = ZOOM_OUT_ICON.getImageData().width; - } - - /** The configuration being previewed */ - private @NonNull Configuration mConfiguration; - - /** Configuration to use if we have an alternate input to be rendered */ - private @NonNull Configuration mAlternateConfiguration; - - /** The associated manager */ - private final @NonNull RenderPreviewManager mManager; - private final @NonNull LayoutCanvas mCanvas; - - private @NonNull SoftReference<ResourceResolver> mResourceResolver = - new SoftReference<ResourceResolver>(null); - private @Nullable Job mJob; - private @Nullable Image mThumbnail; - private @Nullable String mDisplayName; - private int mWidth; - private int mHeight; - private int mX; - private int mY; - private int mTitleHeight; - private double mScale = 1.0; - private double mAspectRatio; - - /** If non null, points to a separate file containing the source */ - private @Nullable IFile mAlternateInput; - - /** If included within another layout, the name of that outer layout */ - private @Nullable Reference mIncludedWithin; - - /** Whether the mouse is actively hovering over this preview */ - private boolean mActive; - - /** - * Whether this preview cannot be rendered because of a model error - such - * as an invalid configuration, a missing resource, an error in the XML - * markup, etc. If non null, contains the error message (or a blank string - * if not known), and null if the render was successful. - */ - private String mError; - - /** Whether in the current layout, this preview is visible */ - private boolean mVisible; - - /** Whether the configuration has changed and needs to be refreshed the next time - * this preview made visible. This corresponds to the change flags in - * {@link ConfigurationClient}. */ - private int mDirty; - - /** - * Creates a new {@linkplain RenderPreview} - * - * @param manager the manager - * @param canvas canvas where preview is painted - * @param configuration the associated configuration - * @param width the initial width to use for the preview - * @param height the initial height to use for the preview - */ - private RenderPreview( - @NonNull RenderPreviewManager manager, - @NonNull LayoutCanvas canvas, - @NonNull Configuration configuration) { - mManager = manager; - mCanvas = canvas; - mConfiguration = configuration; - updateSize(); - - // Should only attempt to create configurations for fully configured devices - assert mConfiguration.getDevice() != null - && mConfiguration.getDeviceState() != null - && mConfiguration.getLocale() != null - && mConfiguration.getTarget() != null - && mConfiguration.getTheme() != null - && mConfiguration.getFullConfig() != null - && mConfiguration.getFullConfig().getScreenSizeQualifier() != null : - mConfiguration; - } - - /** - * Sets the configuration to use for this preview - * - * @param configuration the new configuration - */ - public void setConfiguration(@NonNull Configuration configuration) { - mConfiguration = configuration; - } - - /** - * Gets the scale being applied to the thumbnail - * - * @return the scale being applied to the thumbnail - */ - public double getScale() { - return mScale; - } - - /** - * Sets the scale to apply to the thumbnail - * - * @param scale the factor to scale the thumbnail picture by - */ - public void setScale(double scale) { - disposeThumbnail(); - mScale = scale; - } - - /** - * Returns the aspect ratio of this render preview - * - * @return the aspect ratio - */ - public double getAspectRatio() { - return mAspectRatio; - } - - /** - * Returns whether the preview is actively hovered - * - * @return whether the mouse is hovering over the preview - */ - public boolean isActive() { - return mActive; - } - - /** - * Sets whether the preview is actively hovered - * - * @param active if the mouse is hovering over the preview - */ - public void setActive(boolean active) { - mActive = active; - } - - /** - * Returns whether the preview is visible. Previews that are off - * screen are typically marked invisible during layout, which means we don't - * have to expend effort computing preview thumbnails etc - * - * @return true if the preview is visible - */ - public boolean isVisible() { - return mVisible; - } - - /** - * Returns whether this preview represents a forked layout - * - * @return true if this preview represents a separate file - */ - public boolean isForked() { - return mAlternateInput != null || mIncludedWithin != null; - } - - /** - * Returns the file to be used for this preview, or null if this is not a - * forked layout meaning that the file is the one used in the chooser - * - * @return the file or null for non-forked layouts - */ - @Nullable - public IFile getAlternateInput() { - if (mAlternateInput != null) { - return mAlternateInput; - } else if (mIncludedWithin != null) { - return mIncludedWithin.getFile(); - } - - return null; - } - - /** - * Returns the area of this render preview, PRIOR to scaling - * - * @return the area (width times height without scaling) - */ - int getArea() { - return mWidth * mHeight; - } - - /** - * Sets whether the preview is visible. Previews that are off - * screen are typically marked invisible during layout, which means we don't - * have to expend effort computing preview thumbnails etc - * - * @param visible whether this preview is visible - */ - public void setVisible(boolean visible) { - if (visible != mVisible) { - mVisible = visible; - if (mVisible) { - if (mDirty != 0) { - // Just made the render preview visible: - configurationChanged(mDirty); // schedules render - } else { - updateForkStatus(); - mManager.scheduleRender(this); - } - } else { - dispose(); - } - } - } - - /** - * Sets the layout position relative to the top left corner of the preview - * area, in control coordinates - */ - void setPosition(int x, int y) { - mX = x; - mY = y; - } - - /** - * Gets the layout X position relative to the top left corner of the preview - * area, in control coordinates - */ - int getX() { - return mX; - } - - /** - * Gets the layout Y position relative to the top left corner of the preview - * area, in control coordinates - */ - int getY() { - return mY; - } - - /** Determine whether this configuration has a better match in a different layout file */ - private void updateForkStatus() { - ConfigurationChooser chooser = mManager.getChooser(); - FolderConfiguration config = mConfiguration.getFullConfig(); - if (mAlternateInput != null && chooser.isBestMatchFor(mAlternateInput, config)) { - return; - } - - mAlternateInput = null; - IFile editedFile = chooser.getEditedFile(); - if (editedFile != null) { - if (!chooser.isBestMatchFor(editedFile, config)) { - ProjectResources resources = chooser.getResources(); - if (resources != null) { - ResourceFile best = resources.getMatchingFile(editedFile.getName(), - ResourceType.LAYOUT, config); - if (best != null) { - IAbstractFile file = best.getFile(); - if (file instanceof IFileWrapper) { - mAlternateInput = ((IFileWrapper) file).getIFile(); - } else if (file instanceof File) { - mAlternateInput = AdtUtils.fileToIFile(((File) file)); - } - } - } - if (mAlternateInput != null) { - mAlternateConfiguration = Configuration.create(mConfiguration, - mAlternateInput); - } - } - } - } - - /** - * Creates a new {@linkplain RenderPreview} - * - * @param manager the manager - * @param configuration the associated configuration - * @return a new configuration - */ - @NonNull - public static RenderPreview create( - @NonNull RenderPreviewManager manager, - @NonNull Configuration configuration) { - LayoutCanvas canvas = manager.getCanvas(); - return new RenderPreview(manager, canvas, configuration); - } - - /** - * Throws away this preview: cancels any pending rendering jobs and disposes - * of image resources etc - */ - public void dispose() { - disposeThumbnail(); - - if (mJob != null) { - mJob.cancel(); - mJob = null; - } - } - - /** Disposes the thumbnail rendering. */ - void disposeThumbnail() { - if (mThumbnail != null) { - mThumbnail.dispose(); - mThumbnail = null; - } - } - - /** - * Returns the display name of this preview - * - * @return the name of the preview - */ - @NonNull - public String getDisplayName() { - if (mDisplayName == null) { - String displayName = getConfiguration().getDisplayName(); - if (displayName == null) { - // No display name: this must be the configuration used by default - // for the view which is originally displayed (before adding thumbnails), - // and you've switched away to something else; now we need to display a name - // for this original configuration. For now, just call it "Original" - return "Original"; - } - - return displayName; - } - - return mDisplayName; - } - - /** - * Sets the display name of this preview. By default, the display name is - * the display name of the configuration, but it can be overridden by calling - * this setter (which only sets the preview name, without editing the configuration.) - * - * @param displayName the new display name - */ - public void setDisplayName(@NonNull String displayName) { - mDisplayName = displayName; - } - - /** - * Sets an inclusion context to use for this layout, if any. This will render - * the configuration preview as the outer layout with the current layout - * embedded within. - * - * @param includedWithin a reference to a layout which includes this one - */ - public void setIncludedWithin(Reference includedWithin) { - mIncludedWithin = includedWithin; - } - - /** - * Request a new render after the given delay - * - * @param delay the delay to wait before starting the render job - */ - public void render(long delay) { - Job job = mJob; - if (job != null) { - job.cancel(); - } - if (RENDER_ASYNC) { - job = new AsyncRenderJob(); - } else { - job = new RenderJob(); - } - job.schedule(delay); - job.addJobChangeListener(this); - mJob = job; - } - - /** Render immediately */ - private void renderSync() { - GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor(); - if (editor.getReadyLayoutLib(false /*displayError*/) == null) { - // Don't attempt to render when there is no ready layout library: most likely - // the targets are loading/reloading. - return; - } - - disposeThumbnail(); - - Configuration configuration = - mAlternateInput != null && mAlternateConfiguration != null - ? mAlternateConfiguration : mConfiguration; - ResourceResolver resolver = getResourceResolver(configuration); - RenderService renderService = RenderService.create(editor, configuration, resolver); - - if (mIncludedWithin != null) { - renderService.setIncludedWithin(mIncludedWithin); - } - - if (mAlternateInput != null) { - IAndroidTarget target = editor.getRenderingTarget(); - AndroidTargetData data = null; - if (target != null) { - Sdk sdk = Sdk.getCurrent(); - if (sdk != null) { - data = sdk.getTargetData(target); - } - } - - // Construct UI model from XML - DocumentDescriptor documentDescriptor; - if (data == null) { - documentDescriptor = new DocumentDescriptor("temp", null);//$NON-NLS-1$ - } else { - documentDescriptor = data.getLayoutDescriptors().getDescriptor(); - } - UiDocumentNode model = (UiDocumentNode) documentDescriptor.createUiNode(); - model.setEditor(mCanvas.getEditorDelegate().getEditor()); - model.setUnknownDescriptorProvider(editor.getModel().getUnknownDescriptorProvider()); - - Document document = DomUtilities.getDocument(mAlternateInput); - if (document == null) { - mError = "No document"; - createErrorThumbnail(); - return; - } - model.loadFromXmlNode(document); - renderService.setModel(model); - } else { - renderService.setModel(editor.getModel()); - } - RenderLogger log = editor.createRenderLogger(getDisplayName()); - renderService.setLog(log); - RenderSession session = renderService.createRenderSession(); - Result render = session.render(1000); - - if (DUMP_RENDER_DIAGNOSTICS) { - if (log.hasProblems() || !render.isSuccess()) { - AdtPlugin.log(IStatus.ERROR, "Found problems rendering preview " - + getDisplayName() + ": " - + render.getErrorMessage() + " : " - + log.getProblems(false)); - Throwable exception = render.getException(); - if (exception != null) { - AdtPlugin.log(exception, "Failure rendering preview " + getDisplayName()); - } - } - } - - if (render.isSuccess()) { - mError = null; - } else { - mError = render.getErrorMessage(); - if (mError == null) { - mError = ""; - } - } - - if (render.getStatus() == Status.ERROR_TIMEOUT) { - // TODO: Special handling? schedule update again later - return; - } - if (render.isSuccess()) { - BufferedImage image = session.getImage(); - if (image != null) { - createThumbnail(image); - } - } - - if (mError != null) { - createErrorThumbnail(); - } - } - - private ResourceResolver getResourceResolver(Configuration configuration) { - ResourceResolver resourceResolver = mResourceResolver.get(); - if (resourceResolver != null) { - return resourceResolver; - } - - GraphicalEditorPart graphicalEditor = mCanvas.getEditorDelegate().getGraphicalEditor(); - String theme = configuration.getTheme(); - if (theme == null) { - return null; - } - - Map<ResourceType, Map<String, ResourceValue>> configuredFrameworkRes = null; - Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes = null; - - FolderConfiguration config = configuration.getFullConfig(); - IAndroidTarget target = graphicalEditor.getRenderingTarget(); - ResourceRepository frameworkRes = null; - if (target != null) { - Sdk sdk = Sdk.getCurrent(); - if (sdk == null) { - return null; - } - AndroidTargetData data = sdk.getTargetData(target); - - if (data != null) { - // TODO: SHARE if possible - frameworkRes = data.getFrameworkResources(); - configuredFrameworkRes = frameworkRes.getConfiguredResources(config); - } else { - return null; - } - } else { - return null; - } - assert configuredFrameworkRes != null; - - - // get the resources of the file's project. - ProjectResources projectRes = ResourceManager.getInstance().getProjectResources( - graphicalEditor.getProject()); - configuredProjectRes = projectRes.getConfiguredResources(config); - - if (!theme.startsWith(PREFIX_RESOURCE_REF)) { - if (frameworkRes.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + theme)) { - theme = ANDROID_STYLE_RESOURCE_PREFIX + theme; - } else { - theme = STYLE_RESOURCE_PREFIX + theme; - } - } - - resourceResolver = ResourceResolver.create( - configuredProjectRes, configuredFrameworkRes, - ResourceHelper.styleToTheme(theme), - ResourceHelper.isProjectStyle(theme)); - mResourceResolver = new SoftReference<ResourceResolver>(resourceResolver); - return resourceResolver; - } - - /** - * Sets the new image of the preview and generates a thumbnail - * - * @param image the full size image - */ - void createThumbnail(BufferedImage image) { - if (image == null) { - mThumbnail = null; - return; - } - - ImageOverlay imageOverlay = mCanvas.getImageOverlay(); - boolean drawShadows = imageOverlay == null || imageOverlay.getShowDropShadow(); - double scale = getWidth() / (double) image.getWidth(); - int shadowSize; - if (LARGE_SHADOWS) { - shadowSize = drawShadows ? SHADOW_SIZE : 0; - } else { - shadowSize = drawShadows ? SMALL_SHADOW_SIZE : 0; - } - if (scale < 1.0) { - if (LARGE_SHADOWS) { - image = ImageUtils.scale(image, scale, scale, - shadowSize, shadowSize); - if (drawShadows) { - ImageUtils.drawRectangleShadow(image, 0, 0, - image.getWidth() - shadowSize, - image.getHeight() - shadowSize); - } - } else { - image = ImageUtils.scale(image, scale, scale, - shadowSize, shadowSize); - if (drawShadows) { - ImageUtils.drawSmallRectangleShadow(image, 0, 0, - image.getWidth() - shadowSize, - image.getHeight() - shadowSize); - } - } - } - - mThumbnail = SwtUtils.convertToSwt(mCanvas.getDisplay(), image, - true /* transferAlpha */, -1); - } - - void createErrorThumbnail() { - int shadowSize = LARGE_SHADOWS ? SHADOW_SIZE : SMALL_SHADOW_SIZE; - int width = getWidth(); - int height = getHeight(); - BufferedImage image = new BufferedImage(width + shadowSize, height + shadowSize, - BufferedImage.TYPE_INT_ARGB); - - Graphics2D g = image.createGraphics(); - g.setColor(new java.awt.Color(0xfffbfcc6)); - g.fillRect(0, 0, width, height); - - g.dispose(); - - ImageOverlay imageOverlay = mCanvas.getImageOverlay(); - boolean drawShadows = imageOverlay == null || imageOverlay.getShowDropShadow(); - if (drawShadows) { - if (LARGE_SHADOWS) { - ImageUtils.drawRectangleShadow(image, 0, 0, - image.getWidth() - SHADOW_SIZE, - image.getHeight() - SHADOW_SIZE); - } else { - ImageUtils.drawSmallRectangleShadow(image, 0, 0, - image.getWidth() - SMALL_SHADOW_SIZE, - image.getHeight() - SMALL_SHADOW_SIZE); - } - } - - mThumbnail = SwtUtils.convertToSwt(mCanvas.getDisplay(), image, - true /* transferAlpha */, -1); - } - - private static double getScale(int width, int height) { - int maxWidth = RenderPreviewManager.getMaxWidth(); - int maxHeight = RenderPreviewManager.getMaxHeight(); - if (width > 0 && height > 0 - && (width > maxWidth || height > maxHeight)) { - if (width >= height) { // landscape - return maxWidth / (double) width; - } else { // portrait - return maxHeight / (double) height; - } - } - - return 1.0; - } - - /** - * Returns the width of the preview, in pixels - * - * @return the width in pixels - */ - public int getWidth() { - return (int) (mWidth * mScale * RenderPreviewManager.getScale()); - } - - /** - * Returns the height of the preview, in pixels - * - * @return the height in pixels - */ - public int getHeight() { - return (int) (mHeight * mScale * RenderPreviewManager.getScale()); - } - - /** - * Handles clicks within the preview (x and y are positions relative within the - * preview - * - * @param x the x coordinate within the preview where the click occurred - * @param y the y coordinate within the preview where the click occurred - * @return true if this preview handled (and therefore consumed) the click - */ - public boolean click(int x, int y) { - if (y >= mTitleHeight && y < mTitleHeight + HEADER_HEIGHT) { - int left = 0; - left += CLOSE_ICON_WIDTH; - if (x <= left) { - // Delete - mManager.deletePreview(this); - return true; - } - left += ZOOM_IN_ICON_WIDTH; - if (x <= left) { - // Zoom in - mScale = mScale * (1 / 0.5); - if (Math.abs(mScale-1.0) < 0.0001) { - mScale = 1.0; - } - - render(0); - mManager.layout(true); - mCanvas.redraw(); - return true; - } - left += ZOOM_OUT_ICON_WIDTH; - if (x <= left) { - // Zoom out - mScale = mScale * (0.5 / 1); - if (Math.abs(mScale-1.0) < 0.0001) { - mScale = 1.0; - } - render(0); - - mManager.layout(true); - mCanvas.redraw(); - return true; - } - left += EDIT_ICON_WIDTH; - if (x <= left) { - // Edit. For now, just rename - InputDialog d = new InputDialog( - AdtPlugin.getShell(), - "Rename Preview", // title - "Name:", - getDisplayName(), - null); - if (d.open() == Window.OK) { - String newName = d.getValue(); - mConfiguration.setDisplayName(newName); - if (mDescription != null) { - mManager.rename(mDescription, newName); - } - mCanvas.redraw(); - } - - return true; - } - - // Clicked anywhere else on header - // Perhaps open Edit dialog here? - } - - mManager.switchTo(this); - return true; - } - - /** - * Paints the preview at the given x/y position - * - * @param gc the graphics context to paint it into - * @param x the x coordinate to paint the preview at - * @param y the y coordinate to paint the preview at - */ - void paint(GC gc, int x, int y) { - mTitleHeight = paintTitle(gc, x, y, true /*showFile*/); - y += mTitleHeight; - y += 2; - - int width = getWidth(); - int height = getHeight(); - if (mThumbnail != null && mError == null) { - gc.drawImage(mThumbnail, x, y); - - if (mActive) { - int oldWidth = gc.getLineWidth(); - gc.setLineWidth(3); - gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_LIST_SELECTION)); - gc.drawRectangle(x - 1, y - 1, width + 2, height + 2); - gc.setLineWidth(oldWidth); - } - } else if (mError != null) { - if (mThumbnail != null) { - gc.drawImage(mThumbnail, x, y); - } else { - gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_BORDER)); - gc.drawRectangle(x, y, width, height); - } - - gc.setClipping(x, y, width, height); - Image icon = IconFactory.getInstance().getIcon("renderError"); //$NON-NLS-1$ - ImageData data = icon.getImageData(); - int prevAlpha = gc.getAlpha(); - int alpha = 96; - if (mThumbnail != null) { - alpha -= 32; - } - gc.setAlpha(alpha); - gc.drawImage(icon, x + (width - data.width) / 2, y + (height - data.height) / 2); - - String msg = mError; - Density density = mConfiguration.getDensity(); - if (density == Density.TV || density == Density.LOW) { - msg = "Broken rendering library; unsupported DPI. Try using the SDK manager " + - "to get updated layout libraries."; - } - int charWidth = gc.getFontMetrics().getAverageCharWidth(); - int charsPerLine = (width - 10) / charWidth; - msg = SdkUtils.wrap(msg, charsPerLine, null); - gc.setAlpha(255); - gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_BLACK)); - gc.drawText(msg, x + 5, y + HEADER_HEIGHT, true); - gc.setAlpha(prevAlpha); - gc.setClipping((Region) null); - } else { - gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_BORDER)); - gc.drawRectangle(x, y, width, height); - - Image icon = IconFactory.getInstance().getIcon("refreshPreview"); //$NON-NLS-1$ - ImageData data = icon.getImageData(); - int prevAlpha = gc.getAlpha(); - gc.setAlpha(96); - gc.drawImage(icon, x + (width - data.width) / 2, - y + (height - data.height) / 2); - gc.setAlpha(prevAlpha); - } - - if (mActive) { - int left = x ; - int prevAlpha = gc.getAlpha(); - gc.setAlpha(208); - Color bg = mCanvas.getDisplay().getSystemColor(SWT.COLOR_WHITE); - gc.setBackground(bg); - gc.fillRectangle(left, y, x + width - left, HEADER_HEIGHT); - gc.setAlpha(prevAlpha); - - y += 2; - - // Paint icons - gc.drawImage(CLOSE_ICON, left, y); - left += CLOSE_ICON_WIDTH; - - gc.drawImage(ZOOM_IN_ICON, left, y); - left += ZOOM_IN_ICON_WIDTH; - - gc.drawImage(ZOOM_OUT_ICON, left, y); - left += ZOOM_OUT_ICON_WIDTH; - - gc.drawImage(EDIT_ICON, left, y); - left += EDIT_ICON_WIDTH; - } - } - - /** - * Paints the preview title at the given position (and returns the required - * height) - * - * @param gc the graphics context to paint into - * @param x the left edge of the preview rectangle - * @param y the top edge of the preview rectangle - */ - private int paintTitle(GC gc, int x, int y, boolean showFile) { - String displayName = getDisplayName(); - return paintTitle(gc, x, y, showFile, displayName); - } - - /** - * Paints the preview title at the given position (and returns the required - * height) - * - * @param gc the graphics context to paint into - * @param x the left edge of the preview rectangle - * @param y the top edge of the preview rectangle - * @param displayName the title string to be used - */ - int paintTitle(GC gc, int x, int y, boolean showFile, String displayName) { - int titleHeight = 0; - - if (showFile && mIncludedWithin != null) { - if (mManager.getMode() != INCLUDES) { - displayName = "<include>"; - } else { - // Skip: just paint footer instead - displayName = null; - } - } - - int width = getWidth(); - int labelTop = y + 1; - gc.setClipping(x, labelTop, width, 100); - - // Use font height rather than extent height since we want two adjacent - // previews (which may have different display names and therefore end - // up with slightly different extent heights) to have identical title - // heights such that they are aligned identically - int fontHeight = gc.getFontMetrics().getHeight(); - - if (displayName != null && displayName.length() > 0) { - gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WHITE)); - Point extent = gc.textExtent(displayName); - int labelLeft = Math.max(x, x + (width - extent.x) / 2); - Image icon = null; - Locale locale = mConfiguration.getLocale(); - if (locale != null && (locale.hasLanguage() || locale.hasRegion()) - && (!(mConfiguration instanceof NestedConfiguration) - || ((NestedConfiguration) mConfiguration).isOverridingLocale())) { - icon = locale.getFlagImage(); - } - - if (icon != null) { - int flagWidth = icon.getImageData().width; - int flagHeight = icon.getImageData().height; - labelLeft = Math.max(x + flagWidth / 2, labelLeft); - gc.drawImage(icon, labelLeft - flagWidth / 2 - 1, labelTop); - labelLeft += flagWidth / 2 + 1; - gc.drawText(displayName, labelLeft, - labelTop - (extent.y - flagHeight) / 2, true); - } else { - gc.drawText(displayName, labelLeft, labelTop, true); - } - - labelTop += extent.y; - titleHeight += fontHeight; - } - - if (showFile && (mAlternateInput != null || mIncludedWithin != null)) { - // Draw file flag, and parent folder name - IFile file = mAlternateInput != null - ? mAlternateInput : mIncludedWithin.getFile(); - String fileName = file.getParent().getName() + File.separator - + file.getName(); - Point extent = gc.textExtent(fileName); - Image icon = IconFactory.getInstance().getIcon("android_file"); //$NON-NLS-1$ - int flagWidth = icon.getImageData().width; - int flagHeight = icon.getImageData().height; - - int labelLeft = Math.max(x, x + (width - extent.x - flagWidth - 1) / 2); - - gc.drawImage(icon, labelLeft, labelTop); - - gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY)); - labelLeft += flagWidth + 1; - labelTop -= (extent.y - flagHeight) / 2; - gc.drawText(fileName, labelLeft, labelTop, true); - - titleHeight += Math.max(titleHeight, icon.getImageData().height); - } - - gc.setClipping((Region) null); - - return titleHeight; - } - - /** - * Notifies that the preview's configuration has changed. - * - * @param flags the change flags, a bitmask corresponding to the - * {@code CHANGE_} constants in {@link ConfigurationClient} - */ - public void configurationChanged(int flags) { - if (!mVisible) { - mDirty |= flags; - return; - } - - if ((flags & MASK_RENDERING) != 0) { - mResourceResolver.clear(); - // Handle inheritance - mConfiguration.syncFolderConfig(); - updateForkStatus(); - updateSize(); - } - - // Sanity check to make sure things are working correctly - if (DEBUG) { - RenderPreviewMode mode = mManager.getMode(); - if (mode == DEFAULT) { - assert mConfiguration instanceof VaryingConfiguration; - VaryingConfiguration config = (VaryingConfiguration) mConfiguration; - int alternateFlags = config.getAlternateFlags(); - switch (alternateFlags) { - case Configuration.CFG_DEVICE_STATE: { - State configState = config.getDeviceState(); - State chooserState = mManager.getChooser().getConfiguration() - .getDeviceState(); - assert configState != null && chooserState != null; - assert !configState.getName().equals(chooserState.getName()) - : configState.toString() + ':' + chooserState; - - Device configDevice = config.getDevice(); - Device chooserDevice = mManager.getChooser().getConfiguration() - .getDevice(); - assert configDevice != null && chooserDevice != null; - assert configDevice == chooserDevice - : configDevice.toString() + ':' + chooserDevice; - - break; - } - case Configuration.CFG_DEVICE: { - Device configDevice = config.getDevice(); - Device chooserDevice = mManager.getChooser().getConfiguration() - .getDevice(); - assert configDevice != null && chooserDevice != null; - assert configDevice != chooserDevice - : configDevice.toString() + ':' + chooserDevice; - - State configState = config.getDeviceState(); - State chooserState = mManager.getChooser().getConfiguration() - .getDeviceState(); - assert configState != null && chooserState != null; - assert configState.getName().equals(chooserState.getName()) - : configState.toString() + ':' + chooserState; - - break; - } - case Configuration.CFG_LOCALE: { - Locale configLocale = config.getLocale(); - Locale chooserLocale = mManager.getChooser().getConfiguration() - .getLocale(); - assert configLocale != null && chooserLocale != null; - assert configLocale != chooserLocale - : configLocale.toString() + ':' + chooserLocale; - break; - } - default: { - // Some other type of override I didn't anticipate - assert false : alternateFlags; - } - } - } - } - - mDirty = 0; - mManager.scheduleRender(this); - } - - private void updateSize() { - Device device = mConfiguration.getDevice(); - if (device == null) { - return; - } - Screen screen = device.getDefaultHardware().getScreen(); - if (screen == null) { - return; - } - - FolderConfiguration folderConfig = mConfiguration.getFullConfig(); - ScreenOrientationQualifier qualifier = folderConfig.getScreenOrientationQualifier(); - ScreenOrientation orientation = qualifier == null - ? ScreenOrientation.PORTRAIT : qualifier.getValue(); - - // compute width and height to take orientation into account. - int x = screen.getXDimension(); - int y = screen.getYDimension(); - int screenWidth, screenHeight; - - if (x > y) { - if (orientation == ScreenOrientation.LANDSCAPE) { - screenWidth = x; - screenHeight = y; - } else { - screenWidth = y; - screenHeight = x; - } - } else { - if (orientation == ScreenOrientation.LANDSCAPE) { - screenWidth = y; - screenHeight = x; - } else { - screenWidth = x; - screenHeight = y; - } - } - - int width = RenderPreviewManager.getMaxWidth(); - int height = RenderPreviewManager.getMaxHeight(); - if (screenWidth > 0) { - double scale = getScale(screenWidth, screenHeight); - width = (int) (screenWidth * scale); - height = (int) (screenHeight * scale); - } - - if (width != mWidth || height != mHeight) { - mWidth = width; - mHeight = height; - - Image thumbnail = mThumbnail; - mThumbnail = null; - if (thumbnail != null) { - thumbnail.dispose(); - } - if (mHeight != 0) { - mAspectRatio = mWidth / (double) mHeight; - } - } - } - - /** - * Returns the configuration associated with this preview - * - * @return the configuration - */ - @NonNull - public Configuration getConfiguration() { - return mConfiguration; - } - - // ---- Implements IJobChangeListener ---- - - @Override - public void aboutToRun(IJobChangeEvent event) { - } - - @Override - public void awake(IJobChangeEvent event) { - } - - @Override - public void done(IJobChangeEvent event) { - mJob = null; - } - - @Override - public void running(IJobChangeEvent event) { - } - - @Override - public void scheduled(IJobChangeEvent event) { - } - - @Override - public void sleeping(IJobChangeEvent event) { - } - - // ---- Delayed Rendering ---- - - private final class RenderJob extends UIJob { - public RenderJob() { - super("RenderPreview"); - setSystem(true); - setUser(false); - } - - @Override - public IStatus runInUIThread(IProgressMonitor monitor) { - mJob = null; - if (!mCanvas.isDisposed()) { - renderSync(); - mCanvas.redraw(); - return org.eclipse.core.runtime.Status.OK_STATUS; - } - - return org.eclipse.core.runtime.Status.CANCEL_STATUS; - } - - @Override - public Display getDisplay() { - if (mCanvas.isDisposed()) { - return null; - } - return mCanvas.getDisplay(); - } - } - - private final class AsyncRenderJob extends Job { - public AsyncRenderJob() { - super("RenderPreview"); - setSystem(true); - setUser(false); - } - - @Override - protected IStatus run(IProgressMonitor monitor) { - mJob = null; - - if (mCanvas.isDisposed()) { - return org.eclipse.core.runtime.Status.CANCEL_STATUS; - } - - renderSync(); - - // Update display - mCanvas.getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - mCanvas.redraw(); - } - }); - - return org.eclipse.core.runtime.Status.OK_STATUS; - } - } - - /** - * Sets the input file to use for rendering. If not set, this will just be - * the same file as the configuration chooser. This is used to render other - * layouts, such as variations of the currently edited layout, which are - * not kept in sync with the main layout. - * - * @param file the file to set as input - */ - public void setAlternateInput(@Nullable IFile file) { - mAlternateInput = file; - } - - /** Corresponding description for this preview if it is a manually added preview */ - private @Nullable ConfigurationDescription mDescription; - - /** - * Sets the description of this preview, if this preview is a manually added preview - * - * @param description the description of this preview - */ - public void setDescription(@Nullable ConfigurationDescription description) { - mDescription = description; - } - - /** - * Returns the description of this preview, if this preview is a manually added preview - * - * @return the description - */ - @Nullable - public ConfigurationDescription getDescription() { - return mDescription; - } - - @Override - public String toString() { - return getDisplayName() + ':' + mConfiguration; - } - - /** Sorts render previews into increasing aspect ratio order */ - static Comparator<RenderPreview> INCREASING_ASPECT_RATIO = new Comparator<RenderPreview>() { - @Override - public int compare(RenderPreview preview1, RenderPreview preview2) { - return (int) Math.signum(preview1.mAspectRatio - preview2.mAspectRatio); - } - }; - /** Sorts render previews into visual order: row by row, column by column */ - static Comparator<RenderPreview> VISUAL_ORDER = new Comparator<RenderPreview>() { - @Override - public int compare(RenderPreview preview1, RenderPreview preview2) { - int delta = preview1.mY - preview2.mY; - if (delta == 0) { - delta = preview1.mX - preview2.mX; - } - return delta; - } - }; -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java deleted file mode 100644 index 2bcdba382..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.NonNull; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription; -import com.android.sdklib.devices.Device; -import com.google.common.base.Charsets; -import com.google.common.collect.Lists; -import com.google.common.io.Files; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.QualifiedName; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** A list of render previews */ -class RenderPreviewList { - /** Name of file saved in project directory storing previews */ - private static final String PREVIEW_FILE_NAME = "previews.xml"; //$NON-NLS-1$ - - /** Qualified name for the per-project persistent property include-map */ - private final static QualifiedName PREVIEW_LIST = new QualifiedName(AdtPlugin.PLUGIN_ID, - "previewlist");//$NON-NLS-1$ - - private final IProject mProject; - private final List<ConfigurationDescription> mList = Lists.newArrayList(); - - private RenderPreviewList(@NonNull IProject project) { - mProject = project; - } - - /** - * Returns the {@link RenderPreviewList} for the given project - * - * @param project the project the list is associated with - * @return a {@link RenderPreviewList} for the given project, never null - */ - @NonNull - public static RenderPreviewList get(@NonNull IProject project) { - RenderPreviewList list = null; - try { - list = (RenderPreviewList) project.getSessionProperty(PREVIEW_LIST); - } catch (CoreException e) { - // Not a problem; we will just create a new one - } - - if (list == null) { - list = new RenderPreviewList(project); - try { - project.setSessionProperty(PREVIEW_LIST, list); - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - } - - return list; - } - - private File getManualFile() { - return new File(AdtUtils.getAbsolutePath(mProject).toFile(), PREVIEW_FILE_NAME); - } - - void load(Collection<Device> deviceList) throws IOException { - File file = getManualFile(); - if (file.exists()) { - load(file, deviceList); - } - } - - void save() throws IOException { - deleteFile(); - if (!mList.isEmpty()) { - File file = getManualFile(); - save(file); - } - } - - private void save(File file) throws IOException { - //Document document = DomUtilities.createEmptyPlainDocument(); - Document document = DomUtilities.createEmptyDocument(); - if (document != null) { - for (ConfigurationDescription description : mList) { - description.toXml(document); - } - String xml = EclipseXmlPrettyPrinter.prettyPrint(document, true); - Files.write(xml, file, Charsets.UTF_8); - } - } - - void load(File file, Collection<Device> deviceList) throws IOException { - mList.clear(); - - String xml = Files.toString(file, Charsets.UTF_8); - Document document = DomUtilities.parseDocument(xml, true); - if (document == null || document.getDocumentElement() == null) { - return; - } - List<Element> elements = DomUtilities.getChildren(document.getDocumentElement()); - for (Element element : elements) { - ConfigurationDescription description = ConfigurationDescription.fromXml( - mProject, element, deviceList); - if (description != null) { - mList.add(description); - } - } - } - - /** - * Create a list of previews for the given canvas that matches the internal - * configuration preview list - * - * @param canvas the associated canvas - * @return a new list of previews linked to the given canvas - */ - @NonNull - List<RenderPreview> createPreviews(LayoutCanvas canvas) { - if (mList.isEmpty()) { - return new ArrayList<RenderPreview>(); - } - List<RenderPreview> previews = Lists.newArrayList(); - RenderPreviewManager manager = canvas.getPreviewManager(); - ConfigurationChooser chooser = canvas.getEditorDelegate().getGraphicalEditor() - .getConfigurationChooser(); - - Configuration chooserConfig = chooser.getConfiguration(); - for (ConfigurationDescription description : mList) { - Configuration configuration = Configuration.create(chooser); - configuration.setDisplayName(description.displayName); - configuration.setActivity(description.activity); - configuration.setLocale( - description.locale != null ? description.locale : chooserConfig.getLocale(), - true); - // TODO: Make sure this layout isn't in some v-folder which is incompatible - // with this target! - configuration.setTarget( - description.target != null ? description.target : chooserConfig.getTarget(), - true); - configuration.setTheme( - description.theme != null ? description.theme : chooserConfig.getTheme()); - configuration.setDevice( - description.device != null ? description.device : chooserConfig.getDevice(), - true); - configuration.setDeviceState( - description.state != null ? description.state : chooserConfig.getDeviceState(), - true); - configuration.setNightMode( - description.nightMode != null ? description.nightMode - : chooserConfig.getNightMode(), true); - configuration.setUiMode( - description.uiMode != null ? description.uiMode : chooserConfig.getUiMode(), true); - - //configuration.syncFolderConfig(); - configuration.getFullConfig().set(description.folder); - - RenderPreview preview = RenderPreview.create(manager, configuration); - - preview.setDescription(description); - previews.add(preview); - } - - return previews; - } - - void remove(@NonNull RenderPreview preview) { - ConfigurationDescription description = preview.getDescription(); - if (description != null) { - mList.remove(description); - } - } - - boolean isEmpty() { - return mList.isEmpty(); - } - - void add(@NonNull RenderPreview preview) { - Configuration configuration = preview.getConfiguration(); - ConfigurationDescription description = - ConfigurationDescription.fromConfiguration(mProject, configuration); - // RenderPreviews can have display names that aren't reflected in the configuration - description.displayName = preview.getDisplayName(); - mList.add(description); - preview.setDescription(description); - } - - void delete() { - mList.clear(); - deleteFile(); - } - - private void deleteFile() { - File file = getManualFile(); - if (file.exists()) { - file.delete(); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java deleted file mode 100644 index 98dde86e0..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java +++ /dev/null @@ -1,1696 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE_STATE; -import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.MASK_ALL; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SMALL_SHADOW_SIZE; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreview.LARGE_SHADOWS; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.CUSTOM; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.NONE; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.SCREENS; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.Rect; -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.common.resources.configuration.DensityQualifier; -import com.android.ide.common.resources.configuration.DeviceConfigHelper; -import com.android.ide.common.resources.configuration.FolderConfiguration; -import com.android.ide.common.resources.configuration.LocaleQualifier; -import com.android.ide.common.resources.configuration.ScreenSizeQualifier; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Locale; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.NestedConfiguration; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.VaryingConfiguration; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; -import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; -import com.android.resources.Density; -import com.android.resources.ScreenSize; -import com.android.sdklib.devices.Device; -import com.android.sdklib.devices.Screen; -import com.android.sdklib.devices.State; -import com.google.common.collect.Lists; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.jface.dialogs.InputDialog; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.ScrollBar; -import org.eclipse.ui.IWorkbenchPartSite; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.ide.IDE; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -/** - * Manager for the configuration previews, which handles layout computations, - * managing the image buffer cache, etc - */ -public class RenderPreviewManager { - private static double sScale = 1.0; - private static final int RENDER_DELAY = 150; - private static final int PREVIEW_VGAP = 18; - private static final int PREVIEW_HGAP = 12; - private static final int MAX_WIDTH = 200; - private static final int MAX_HEIGHT = MAX_WIDTH; - private static final int ZOOM_ICON_WIDTH = 16; - private static final int ZOOM_ICON_HEIGHT = 16; - private @Nullable List<RenderPreview> mPreviews; - private @Nullable RenderPreviewList mManualList; - private final @NonNull LayoutCanvas mCanvas; - private final @NonNull CanvasTransform mVScale; - private final @NonNull CanvasTransform mHScale; - private int mPrevCanvasWidth; - private int mPrevCanvasHeight; - private int mPrevImageWidth; - private int mPrevImageHeight; - private @NonNull RenderPreviewMode mMode = NONE; - private @Nullable RenderPreview mActivePreview; - private @Nullable ScrollBarListener mListener; - private int mLayoutHeight; - /** Last seen state revision in this {@link RenderPreviewManager}. If less - * than {@link #sRevision}, the previews need to be updated on next exposure */ - private static int mRevision; - /** Current global revision count */ - private static int sRevision; - private boolean mNeedLayout; - private boolean mNeedRender; - private boolean mNeedZoom; - private SwapAnimation mAnimation; - - /** - * Creates a {@link RenderPreviewManager} associated with the given canvas - * - * @param canvas the canvas to manage previews for - */ - public RenderPreviewManager(@NonNull LayoutCanvas canvas) { - mCanvas = canvas; - mHScale = canvas.getHorizontalTransform(); - mVScale = canvas.getVerticalTransform(); - } - - /** - * Revise the global state revision counter. This will cause all layout - * preview managers to refresh themselves to the latest revision when they - * are next exposed. - */ - public static void bumpRevision() { - sRevision++; - } - - /** - * Returns the associated chooser - * - * @return the associated chooser - */ - @NonNull - ConfigurationChooser getChooser() { - GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor(); - return editor.getConfigurationChooser(); - } - - /** - * Returns the associated canvas - * - * @return the canvas - */ - @NonNull - public LayoutCanvas getCanvas() { - return mCanvas; - } - - /** Zooms in (grows all previews) */ - public void zoomIn() { - sScale = sScale * (1 / 0.9); - if (Math.abs(sScale-1.0) < 0.0001) { - sScale = 1.0; - } - - updatedZoom(); - } - - /** Zooms out (shrinks all previews) */ - public void zoomOut() { - sScale = sScale * (0.9 / 1); - if (Math.abs(sScale-1.0) < 0.0001) { - sScale = 1.0; - } - updatedZoom(); - } - - /** Zooms to 100 (resets zoom) */ - public void zoomReset() { - sScale = 1.0; - updatedZoom(); - mNeedZoom = mNeedLayout = true; - mCanvas.redraw(); - } - - private void updatedZoom() { - if (hasPreviews()) { - for (RenderPreview preview : mPreviews) { - preview.disposeThumbnail(); - } - RenderPreview preview = mCanvas.getPreview(); - if (preview != null) { - preview.disposeThumbnail(); - } - } - - mNeedLayout = mNeedRender = true; - mCanvas.redraw(); - } - - static int getMaxWidth() { - return (int) (sScale * MAX_WIDTH); - } - - static int getMaxHeight() { - return (int) (sScale * MAX_HEIGHT); - } - - static double getScale() { - return sScale; - } - - /** - * Returns whether there are any manual preview items (provided the current - * mode is manual previews - * - * @return true if there are items in the manual preview list - */ - public boolean hasManualPreviews() { - assert mMode == CUSTOM; - return mManualList != null && !mManualList.isEmpty(); - } - - /** Delete all the previews */ - public void deleteManualPreviews() { - disposePreviews(); - selectMode(NONE); - mCanvas.setFitScale(true /* onlyZoomOut */, true /*allowZoomIn*/); - - if (mManualList != null) { - mManualList.delete(); - } - } - - /** Dispose all the previews */ - public void disposePreviews() { - if (mPreviews != null) { - List<RenderPreview> old = mPreviews; - mPreviews = null; - for (RenderPreview preview : old) { - preview.dispose(); - } - } - } - - /** - * Deletes the given preview - * - * @param preview the preview to be deleted - */ - public void deletePreview(RenderPreview preview) { - mPreviews.remove(preview); - preview.dispose(); - layout(true); - mCanvas.redraw(); - - if (mManualList != null) { - mManualList.remove(preview); - saveList(); - } - } - - /** - * Compute the total width required for the previews, including internal padding - * - * @return total width in pixels - */ - public int computePreviewWidth() { - int maxPreviewWidth = 0; - if (hasPreviews()) { - for (RenderPreview preview : mPreviews) { - maxPreviewWidth = Math.max(maxPreviewWidth, preview.getWidth()); - } - - if (maxPreviewWidth > 0) { - maxPreviewWidth += 2 * PREVIEW_HGAP; // 2x for left and right side - maxPreviewWidth += LARGE_SHADOWS ? SHADOW_SIZE : SMALL_SHADOW_SIZE; - } - - return maxPreviewWidth; - } - - return 0; - } - - /** - * Layout Algorithm. This sets the {@link RenderPreview#getX()} and - * {@link RenderPreview#getY()} coordinates of all the previews. It also - * marks previews as visible or invisible via - * {@link RenderPreview#setVisible(boolean)} according to their position and - * the current visible view port in the layout canvas. Finally, it also sets - * the {@code mLayoutHeight} field, such that the scrollbars can compute the - * right scrolled area, and that scrolling can cause render refreshes on - * views that are made visible. - * <p> - * This is not a traditional bin packing problem, because the objects to be - * packaged do not have a fixed size; we can scale them up and down in order - * to provide an "optimal" size. - * <p> - * See http://en.wikipedia.org/wiki/Packing_problem See - * http://en.wikipedia.org/wiki/Bin_packing_problem - */ - void layout(boolean refresh) { - mNeedLayout = false; - - if (mPreviews == null || mPreviews.isEmpty()) { - return; - } - - int scaledImageWidth = mHScale.getScaledImgSize(); - int scaledImageHeight = mVScale.getScaledImgSize(); - Rectangle clientArea = mCanvas.getClientArea(); - - if (!refresh && - (scaledImageWidth == mPrevImageWidth - && scaledImageHeight == mPrevImageHeight - && clientArea.width == mPrevCanvasWidth - && clientArea.height == mPrevCanvasHeight)) { - // No change - return; - } - - mPrevImageWidth = scaledImageWidth; - mPrevImageHeight = scaledImageHeight; - mPrevCanvasWidth = clientArea.width; - mPrevCanvasHeight = clientArea.height; - - if (mListener == null) { - mListener = new ScrollBarListener(); - mCanvas.getVerticalBar().addSelectionListener(mListener); - } - - beginRenderScheduling(); - - mLayoutHeight = 0; - - if (previewsHaveIdenticalSize() || fixedOrder()) { - // If all the preview boxes are of identical sizes, or if the order is predetermined, - // just lay them out in rows. - rowLayout(); - } else if (previewsFit()) { - layoutFullFit(); - } else { - rowLayout(); - } - - mCanvas.updateScrollBars(); - } - - /** - * Performs a simple layout where the views are laid out in a row, wrapping - * around the top left canvas image. - */ - private void rowLayout() { - // TODO: Separate layout heuristics for portrait and landscape orientations (though - // it also depends on the dimensions of the canvas window, which determines the - // shape of the leftover space) - - int scaledImageWidth = mHScale.getScaledImgSize(); - int scaledImageHeight = mVScale.getScaledImgSize(); - Rectangle clientArea = mCanvas.getClientArea(); - - int availableWidth = clientArea.x + clientArea.width - getX(); - int availableHeight = clientArea.y + clientArea.height - getY(); - int maxVisibleY = clientArea.y + clientArea.height; - - int bottomBorder = scaledImageHeight; - int rightHandSide = scaledImageWidth + PREVIEW_HGAP; - int nextY = 0; - - // First lay out images across the top right hand side - int x = rightHandSide; - int y = 0; - boolean wrapped = false; - - int vgap = PREVIEW_VGAP; - for (RenderPreview preview : mPreviews) { - // If we have forked previews, double the vgap to allow space for two labels - if (preview.isForked()) { - vgap *= 2; - break; - } - } - - List<RenderPreview> aspectOrder; - if (!fixedOrder()) { - aspectOrder = new ArrayList<RenderPreview>(mPreviews); - Collections.sort(aspectOrder, RenderPreview.INCREASING_ASPECT_RATIO); - } else { - aspectOrder = mPreviews; - } - - for (RenderPreview preview : aspectOrder) { - if (x > 0 && x + preview.getWidth() > availableWidth) { - x = rightHandSide; - int prevY = y; - y = nextY; - if ((prevY <= bottomBorder || - y <= bottomBorder) - && Math.max(nextY, y + preview.getHeight()) > bottomBorder) { - // If there's really no visible room below, don't bother - // Similarly, don't wrap individually scaled views - if (bottomBorder < availableHeight - 40 && preview.getScale() < 1.2) { - // If it's closer to the top row than the bottom, just - // mark the next row for left justify instead - if (bottomBorder - y > y + preview.getHeight() - bottomBorder) { - rightHandSide = 0; - wrapped = true; - } else if (!wrapped) { - y = nextY = Math.max(nextY, bottomBorder + vgap); - x = rightHandSide = 0; - wrapped = true; - } - } - } - } - if (x > 0 && y <= bottomBorder - && Math.max(nextY, y + preview.getHeight()) > bottomBorder) { - if (clientArea.height - bottomBorder < preview.getHeight()) { - // No room below the device on the left; just continue on the - // bottom row - } else if (preview.getScale() < 1.2) { - if (bottomBorder - y > y + preview.getHeight() - bottomBorder) { - rightHandSide = 0; - wrapped = true; - } else { - y = nextY = Math.max(nextY, bottomBorder + vgap); - x = rightHandSide = 0; - wrapped = true; - } - } - } - - preview.setPosition(x, y); - - if (y > maxVisibleY && maxVisibleY > 0) { - preview.setVisible(false); - } else if (!preview.isVisible()) { - preview.setVisible(true); - } - - x += preview.getWidth(); - x += PREVIEW_HGAP; - nextY = Math.max(nextY, y + preview.getHeight() + vgap); - } - - mLayoutHeight = nextY; - } - - private boolean fixedOrder() { - return mMode == SCREENS; - } - - /** Returns true if all the previews have the same identical size */ - private boolean previewsHaveIdenticalSize() { - if (!hasPreviews()) { - return true; - } - - Iterator<RenderPreview> iterator = mPreviews.iterator(); - RenderPreview first = iterator.next(); - int width = first.getWidth(); - int height = first.getHeight(); - - while (iterator.hasNext()) { - RenderPreview preview = iterator.next(); - if (width != preview.getWidth() || height != preview.getHeight()) { - return false; - } - } - - return true; - } - - /** Returns true if all the previews can fully fit in the available space */ - private boolean previewsFit() { - int scaledImageWidth = mHScale.getScaledImgSize(); - int scaledImageHeight = mVScale.getScaledImgSize(); - Rectangle clientArea = mCanvas.getClientArea(); - int availableWidth = clientArea.x + clientArea.width - getX(); - int availableHeight = clientArea.y + clientArea.height - getY(); - int bottomBorder = scaledImageHeight; - int rightHandSide = scaledImageWidth + PREVIEW_HGAP; - - // First see if we can fit everything; if so, we can try to make the layouts - // larger such that they fill up all the available space - long availableArea = rightHandSide * bottomBorder + - availableWidth * (Math.max(0, availableHeight - bottomBorder)); - - long requiredArea = 0; - for (RenderPreview preview : mPreviews) { - // Note: This does not include individual preview scale; the layout - // algorithm itself may be tweaking the scales to fit elements within - // the layout - requiredArea += preview.getArea(); - } - - return requiredArea * sScale < availableArea; - } - - private void layoutFullFit() { - int scaledImageWidth = mHScale.getScaledImgSize(); - int scaledImageHeight = mVScale.getScaledImgSize(); - Rectangle clientArea = mCanvas.getClientArea(); - int availableWidth = clientArea.x + clientArea.width - getX(); - int availableHeight = clientArea.y + clientArea.height - getY(); - int maxVisibleY = clientArea.y + clientArea.height; - int bottomBorder = scaledImageHeight; - int rightHandSide = scaledImageWidth + PREVIEW_HGAP; - - int minWidth = Integer.MAX_VALUE; - int minHeight = Integer.MAX_VALUE; - for (RenderPreview preview : mPreviews) { - minWidth = Math.min(minWidth, preview.getWidth()); - minHeight = Math.min(minHeight, preview.getHeight()); - } - - BinPacker packer = new BinPacker(minWidth, minHeight); - - // TODO: Instead of this, just start with client area and occupy scaled image size! - - // Add in gap on right and bottom since we'll add that requirement on the width and - // height rectangles too (for spacing) - packer.addSpace(new Rect(rightHandSide, 0, - availableWidth - rightHandSide + PREVIEW_HGAP, - availableHeight + PREVIEW_VGAP)); - if (maxVisibleY > bottomBorder) { - packer.addSpace(new Rect(0, bottomBorder + PREVIEW_VGAP, - availableWidth + PREVIEW_HGAP, maxVisibleY - bottomBorder + PREVIEW_VGAP)); - } - - // TODO: Sort previews first before attempting to position them? - - ArrayList<RenderPreview> aspectOrder = new ArrayList<RenderPreview>(mPreviews); - Collections.sort(aspectOrder, RenderPreview.INCREASING_ASPECT_RATIO); - - for (RenderPreview preview : aspectOrder) { - int previewWidth = preview.getWidth(); - int previewHeight = preview.getHeight(); - previewHeight += PREVIEW_VGAP; - if (preview.isForked()) { - previewHeight += PREVIEW_VGAP; - } - previewWidth += PREVIEW_HGAP; - // title height? how do I account for that? - Rect position = packer.occupy(previewWidth, previewHeight); - if (position != null) { - preview.setPosition(position.x, position.y); - preview.setVisible(true); - } else { - // Can't fit: give up and do plain row layout - rowLayout(); - return; - } - } - - mLayoutHeight = availableHeight; - } - /** - * Paints the configuration previews - * - * @param gc the graphics context to paint into - */ - void paint(GC gc) { - if (hasPreviews()) { - // Ensure up to date at all times; consider moving if it's too expensive - layout(mNeedLayout); - if (mNeedRender) { - renderPreviews(); - } - if (mNeedZoom) { - boolean allowZoomIn = true /*mMode == NONE*/; - mCanvas.setFitScale(false /*onlyZoomOut*/, allowZoomIn); - mNeedZoom = false; - } - int rootX = getX(); - int rootY = getY(); - - for (RenderPreview preview : mPreviews) { - if (preview.isVisible()) { - int x = rootX + preview.getX(); - int y = rootY + preview.getY(); - preview.paint(gc, x, y); - } - } - - RenderPreview preview = mCanvas.getPreview(); - if (preview != null) { - String displayName = null; - Configuration configuration = preview.getConfiguration(); - if (configuration instanceof VaryingConfiguration) { - // Use override flags from stashed preview, but configuration - // data from live (not varying) configured configuration - VaryingConfiguration cfg = (VaryingConfiguration) configuration; - int flags = cfg.getAlternateFlags() | cfg.getOverrideFlags(); - displayName = NestedConfiguration.computeDisplayName(flags, - getChooser().getConfiguration()); - } else if (configuration instanceof NestedConfiguration) { - int flags = ((NestedConfiguration) configuration).getOverrideFlags(); - displayName = NestedConfiguration.computeDisplayName(flags, - getChooser().getConfiguration()); - } else { - displayName = configuration.getDisplayName(); - } - if (displayName != null) { - CanvasTransform hi = mHScale; - CanvasTransform vi = mVScale; - - int destX = hi.translate(0); - int destY = vi.translate(0); - int destWidth = hi.getScaledImgSize(); - int destHeight = vi.getScaledImgSize(); - - int x = destX + destWidth / 2 - preview.getWidth() / 2; - int y = destY + destHeight; - - preview.paintTitle(gc, x, y, false /*showFile*/, displayName); - } - } - - // Zoom overlay - int x = getZoomX(); - if (x > 0) { - int y = getZoomY(); - int oldAlpha = gc.getAlpha(); - - // Paint background oval rectangle behind the zoom and close icons - gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY)); - gc.setAlpha(128); - int padding = 3; - int arc = 5; - gc.fillRoundRectangle(x - padding, y - padding, - ZOOM_ICON_WIDTH + 2 * padding, - 4 * ZOOM_ICON_HEIGHT + 2 * padding, arc, arc); - - gc.setAlpha(255); - IconFactory iconFactory = IconFactory.getInstance(); - Image zoomOut = iconFactory.getIcon("zoomminus"); //$NON-NLS-1$); - Image zoomIn = iconFactory.getIcon("zoomplus"); //$NON-NLS-1$); - Image zoom100 = iconFactory.getIcon("zoom100"); //$NON-NLS-1$); - Image close = iconFactory.getIcon("close"); //$NON-NLS-1$); - - gc.drawImage(zoomIn, x, y); - y += ZOOM_ICON_HEIGHT; - gc.drawImage(zoomOut, x, y); - y += ZOOM_ICON_HEIGHT; - gc.drawImage(zoom100, x, y); - y += ZOOM_ICON_HEIGHT; - gc.drawImage(close, x, y); - y += ZOOM_ICON_HEIGHT; - gc.setAlpha(oldAlpha); - } - } else if (mMode == CUSTOM) { - int rootX = getX(); - rootX += mHScale.getScaledImgSize(); - rootX += 2 * PREVIEW_HGAP; - int rootY = getY(); - rootY += 20; - gc.setFont(mCanvas.getFont()); - gc.setForeground(mCanvas.getDisplay().getSystemColor(SWT.COLOR_BLACK)); - gc.drawText("Add previews with \"Add as Thumbnail\"\nin the configuration menu", - rootX, rootY, true); - } - - if (mAnimation != null) { - mAnimation.tick(gc); - } - } - - private void addPreview(@NonNull RenderPreview preview) { - if (mPreviews == null) { - mPreviews = Lists.newArrayList(); - } - mPreviews.add(preview); - } - - /** Adds the current configuration as a new configuration preview */ - public void addAsThumbnail() { - ConfigurationChooser chooser = getChooser(); - String name = chooser.getConfiguration().getDisplayName(); - if (name == null || name.isEmpty()) { - name = getUniqueName(); - } - InputDialog d = new InputDialog( - AdtPlugin.getShell(), - "Add as Thumbnail Preview", // title - "Name of thumbnail:", - name, - null); - if (d.open() == Window.OK) { - selectMode(CUSTOM); - - String newName = d.getValue(); - // Create a new configuration from the current settings in the composite - Configuration configuration = Configuration.copy(chooser.getConfiguration()); - configuration.setDisplayName(newName); - - RenderPreview preview = RenderPreview.create(this, configuration); - addPreview(preview); - - layout(true); - beginRenderScheduling(); - scheduleRender(preview); - mCanvas.setFitScale(true /* onlyZoomOut */, false /*allowZoomIn*/); - - if (mManualList == null) { - loadList(); - } - if (mManualList != null) { - mManualList.add(preview); - saveList(); - } - } - } - - /** - * Computes a unique new name for a configuration preview that represents - * the current, default configuration - * - * @return a unique name - */ - private String getUniqueName() { - if (mPreviews == null || mPreviews.isEmpty()) { - // NO, not for the first preview! - return "Config1"; - } - - Set<String> names = new HashSet<String>(mPreviews.size()); - for (RenderPreview preview : mPreviews) { - names.add(preview.getDisplayName()); - } - - int index = 2; - while (true) { - String name = String.format("Config%1$d", index); - if (!names.contains(name)) { - return name; - } - index++; - } - } - - /** Generates a bunch of default configuration preview thumbnails */ - public void addDefaultPreviews() { - ConfigurationChooser chooser = getChooser(); - Configuration parent = chooser.getConfiguration(); - if (parent instanceof NestedConfiguration) { - parent = ((NestedConfiguration) parent).getParent(); - } - if (mCanvas.getImageOverlay().getImage() != null) { - // Create Language variation - createLocaleVariation(chooser, parent); - - // Vary screen size - // TODO: Be smarter here: Pick a screen that is both as differently as possible - // from the current screen as well as also supported. So consider - // things like supported screens, targetSdk etc. - createScreenVariations(parent); - - // Vary orientation - createStateVariation(chooser, parent); - - // Vary render target - createRenderTargetVariation(chooser, parent); - } - - // Also add in include-context previews, if any - addIncludedInPreviews(); - - // Make a placeholder preview for the current screen, in case we switch from it - RenderPreview preview = RenderPreview.create(this, parent); - mCanvas.setPreview(preview); - - sortPreviewsByOrientation(); - } - - private void createRenderTargetVariation(ConfigurationChooser chooser, Configuration parent) { - /* This is disabled for now: need to load multiple versions of layoutlib. - When I did this, there seemed to be some drug interactions between - them, and I would end up with NPEs in layoutlib code which normally works. - VaryingConfiguration configuration = - VaryingConfiguration.create(chooser, parent); - configuration.setAlternatingTarget(true); - configuration.syncFolderConfig(); - addPreview(RenderPreview.create(this, configuration)); - */ - } - - private void createStateVariation(ConfigurationChooser chooser, Configuration parent) { - State currentState = parent.getDeviceState(); - State nextState = parent.getNextDeviceState(currentState); - if (nextState != currentState) { - VaryingConfiguration configuration = - VaryingConfiguration.create(chooser, parent); - configuration.setAlternateDeviceState(true); - configuration.syncFolderConfig(); - addPreview(RenderPreview.create(this, configuration)); - } - } - - private void createLocaleVariation(ConfigurationChooser chooser, Configuration parent) { - LocaleQualifier currentLanguage = parent.getLocale().qualifier; - for (Locale locale : chooser.getLocaleList()) { - LocaleQualifier qualifier = locale.qualifier; - if (!qualifier.getLanguage().equals(currentLanguage.getLanguage())) { - VaryingConfiguration configuration = - VaryingConfiguration.create(chooser, parent); - configuration.setAlternateLocale(true); - configuration.syncFolderConfig(); - addPreview(RenderPreview.create(this, configuration)); - break; - } - } - } - - private void createScreenVariations(Configuration parent) { - ConfigurationChooser chooser = getChooser(); - VaryingConfiguration configuration; - - configuration = VaryingConfiguration.create(chooser, parent); - configuration.setVariation(0); - configuration.setAlternateDevice(true); - configuration.syncFolderConfig(); - addPreview(RenderPreview.create(this, configuration)); - - configuration = VaryingConfiguration.create(chooser, parent); - configuration.setVariation(1); - configuration.setAlternateDevice(true); - configuration.syncFolderConfig(); - addPreview(RenderPreview.create(this, configuration)); - } - - /** - * Returns the current mode as seen by this {@link RenderPreviewManager}. - * Note that it may not yet have been synced with the global mode kept in - * {@link AdtPrefs#getRenderPreviewMode()}. - * - * @return the current preview mode - */ - @NonNull - public RenderPreviewMode getMode() { - return mMode; - } - - /** - * Update the set of previews for the current mode - * - * @param force force a refresh even if the preview type has not changed - * @return true if the views were recomputed, false if the previews were - * already showing and the mode not changed - */ - public boolean recomputePreviews(boolean force) { - RenderPreviewMode newMode = AdtPrefs.getPrefs().getRenderPreviewMode(); - if (newMode == mMode && !force - && (mRevision == sRevision - || mMode == NONE - || mMode == CUSTOM)) { - return false; - } - - RenderPreviewMode oldMode = mMode; - mMode = newMode; - mRevision = sRevision; - - sScale = 1.0; - disposePreviews(); - - switch (mMode) { - case DEFAULT: - addDefaultPreviews(); - break; - case INCLUDES: - addIncludedInPreviews(); - break; - case LOCALES: - addLocalePreviews(); - break; - case SCREENS: - addScreenSizePreviews(); - break; - case VARIATIONS: - addVariationPreviews(); - break; - case CUSTOM: - addManualPreviews(); - break; - case NONE: - // Can't just set mNeedZoom because with no previews, the paint - // method does nothing - mCanvas.setFitScale(false /*onlyZoomOut*/, true /*allowZoomIn*/); - break; - default: - assert false : mMode; - } - - // We schedule layout for the next redraw rather than process it here immediately; - // not only does this let us avoid doing work for windows where the tab is in the - // background, but when a file is opened we may not know the size of the canvas - // yet, and the layout methods need it in order to do a good job. By the time - // the canvas is painted, we have accurate bounds. - mNeedLayout = mNeedRender = true; - mCanvas.redraw(); - - if (oldMode != mMode && (oldMode == NONE || mMode == NONE)) { - // If entering or exiting preview mode: updating padding which is compressed - // only in preview mode. - mCanvas.getHorizontalTransform().refresh(); - mCanvas.getVerticalTransform().refresh(); - } - - return true; - } - - /** - * Sets the new render preview mode to use - * - * @param mode the new mode - */ - public void selectMode(@NonNull RenderPreviewMode mode) { - if (mode != mMode) { - AdtPrefs.getPrefs().setPreviewMode(mode); - recomputePreviews(false); - } - } - - /** Similar to {@link #addDefaultPreviews()} but for locales */ - public void addLocalePreviews() { - - ConfigurationChooser chooser = getChooser(); - List<Locale> locales = chooser.getLocaleList(); - Configuration parent = chooser.getConfiguration(); - - for (Locale locale : locales) { - if (!locale.hasLanguage() && !locale.hasRegion()) { - continue; - } - NestedConfiguration configuration = NestedConfiguration.create(chooser, parent); - configuration.setOverrideLocale(true); - configuration.setLocale(locale, false); - - String displayName = ConfigurationChooser.getLocaleLabel(chooser, locale, false); - assert displayName != null; // it's never non null when locale is non null - configuration.setDisplayName(displayName); - - addPreview(RenderPreview.create(this, configuration)); - } - - // Make a placeholder preview for the current screen, in case we switch from it - Configuration configuration = parent; - Locale locale = configuration.getLocale(); - String label = ConfigurationChooser.getLocaleLabel(chooser, locale, false); - if (label == null) { - label = "default"; - } - configuration.setDisplayName(label); - RenderPreview preview = RenderPreview.create(this, parent); - if (preview != null) { - mCanvas.setPreview(preview); - } - - // No need to sort: they should all be identical - } - - /** Similar to {@link #addDefaultPreviews()} but for screen sizes */ - public void addScreenSizePreviews() { - ConfigurationChooser chooser = getChooser(); - Collection<Device> devices = chooser.getDevices(); - Configuration configuration = chooser.getConfiguration(); - boolean canScaleNinePatch = configuration.supports(Capability.FIXED_SCALABLE_NINE_PATCH); - - // Rearrange the devices a bit such that the most interesting devices bubble - // to the front - // 10" tablet, 7" tablet, reference phones, tiny phone, and in general the first - // version of each seen screen size - List<Device> sorted = new ArrayList<Device>(devices); - Set<ScreenSize> seenSizes = new HashSet<ScreenSize>(); - State currentState = configuration.getDeviceState(); - String currentStateName = currentState != null ? currentState.getName() : ""; - - for (int i = 0, n = sorted.size(); i < n; i++) { - Device device = sorted.get(i); - boolean interesting = false; - - State state = device.getState(currentStateName); - if (state == null) { - state = device.getAllStates().get(0); - } - - if (device.getName().startsWith("Nexus ") //$NON-NLS-1$ - || device.getName().endsWith(" Nexus")) { //$NON-NLS-1$ - // Not String#contains("Nexus") because that would also pick up all the generic - // entries ("3.7in WVGA (Nexus One)") so we'd have them duplicated - interesting = true; - } - - FolderConfiguration c = DeviceConfigHelper.getFolderConfig(state); - if (c != null) { - ScreenSizeQualifier sizeQualifier = c.getScreenSizeQualifier(); - if (sizeQualifier != null) { - ScreenSize size = sizeQualifier.getValue(); - if (!seenSizes.contains(size)) { - seenSizes.add(size); - interesting = true; - } - } - - // Omit LDPI, not really used anymore - DensityQualifier density = c.getDensityQualifier(); - if (density != null) { - Density d = density.getValue(); - if (d == Density.LOW) { - interesting = false; - } - - if (!canScaleNinePatch && d == Density.TV) { - interesting = false; - } - } - } - - if (interesting) { - NestedConfiguration screenConfig = NestedConfiguration.create(chooser, - configuration); - screenConfig.setOverrideDevice(true); - screenConfig.setDevice(device, true); - screenConfig.syncFolderConfig(); - screenConfig.setDisplayName(ConfigurationChooser.getDeviceLabel(device, true)); - addPreview(RenderPreview.create(this, screenConfig)); - } - } - - // Sorted by screen size, in decreasing order - sortPreviewsByScreenSize(); - } - - /** - * Previews this layout as included in other layouts - */ - public void addIncludedInPreviews() { - ConfigurationChooser chooser = getChooser(); - IProject project = chooser.getProject(); - if (project == null) { - return; - } - IncludeFinder finder = IncludeFinder.get(project); - - final List<Reference> includedBy = finder.getIncludedBy(chooser.getEditedFile()); - - if (includedBy == null || includedBy.isEmpty()) { - // TODO: Generate some useful defaults, such as including it in a ListView - // as the list item layout? - return; - } - - for (final Reference reference : includedBy) { - String title = reference.getDisplayName(); - Configuration config = Configuration.create(chooser.getConfiguration(), - reference.getFile()); - RenderPreview preview = RenderPreview.create(this, config); - preview.setDisplayName(title); - preview.setIncludedWithin(reference); - - addPreview(preview); - } - - sortPreviewsByOrientation(); - } - - /** - * Previews this layout as included in other layouts - */ - public void addVariationPreviews() { - ConfigurationChooser chooser = getChooser(); - - IFile file = chooser.getEditedFile(); - List<IFile> variations = AdtUtils.getResourceVariations(file, false /*includeSelf*/); - - // Sort by parent folder - Collections.sort(variations, new Comparator<IFile>() { - @Override - public int compare(IFile file1, IFile file2) { - return file1.getParent().getName().compareTo(file2.getParent().getName()); - } - }); - - Configuration currentConfig = chooser.getConfiguration(); - - for (IFile variation : variations) { - String title = variation.getParent().getName(); - Configuration config = Configuration.create(chooser.getConfiguration(), variation); - config.setTheme(currentConfig.getTheme()); - config.setActivity(currentConfig.getActivity()); - RenderPreview preview = RenderPreview.create(this, config); - preview.setDisplayName(title); - preview.setAlternateInput(variation); - - addPreview(preview); - } - - sortPreviewsByOrientation(); - } - - /** - * Previews this layout using a custom configured set of layouts - */ - public void addManualPreviews() { - if (mManualList == null) { - loadList(); - } else { - mPreviews = mManualList.createPreviews(mCanvas); - } - } - - private void loadList() { - IProject project = getChooser().getProject(); - if (project == null) { - return; - } - - if (mManualList == null) { - mManualList = RenderPreviewList.get(project); - } - - try { - mManualList.load(getChooser().getDevices()); - mPreviews = mManualList.createPreviews(mCanvas); - } catch (IOException e) { - AdtPlugin.log(e, null); - } - } - - private void saveList() { - if (mManualList != null) { - try { - mManualList.save(); - } catch (IOException e) { - AdtPlugin.log(e, null); - } - } - } - - void rename(ConfigurationDescription description, String newName) { - IProject project = getChooser().getProject(); - if (project == null) { - return; - } - - if (mManualList == null) { - mManualList = RenderPreviewList.get(project); - } - description.displayName = newName; - saveList(); - } - - - /** - * Notifies that the main configuration has changed. - * - * @param flags the change flags, a bitmask corresponding to the - * {@code CHANGE_} constants in {@link ConfigurationClient} - */ - public void configurationChanged(int flags) { - // Similar to renderPreviews, but only acts on incomplete previews - if (hasPreviews()) { - // Do zoomed images first - beginRenderScheduling(); - for (RenderPreview preview : mPreviews) { - if (preview.getScale() > 1.2) { - preview.configurationChanged(flags); - } - } - for (RenderPreview preview : mPreviews) { - if (preview.getScale() <= 1.2) { - preview.configurationChanged(flags); - } - } - RenderPreview preview = mCanvas.getPreview(); - if (preview != null) { - preview.configurationChanged(flags); - preview.dispose(); - } - mNeedLayout = true; - mCanvas.redraw(); - } - } - - /** Updates the configuration preview thumbnails */ - public void renderPreviews() { - if (hasPreviews()) { - beginRenderScheduling(); - - // Process in visual order - ArrayList<RenderPreview> visualOrder = new ArrayList<RenderPreview>(mPreviews); - Collections.sort(visualOrder, RenderPreview.VISUAL_ORDER); - - // Do zoomed images first - for (RenderPreview preview : visualOrder) { - if (preview.getScale() > 1.2 && preview.isVisible()) { - scheduleRender(preview); - } - } - // Non-zoomed images - for (RenderPreview preview : visualOrder) { - if (preview.getScale() <= 1.2 && preview.isVisible()) { - scheduleRender(preview); - } - } - } - - mNeedRender = false; - } - - private int mPendingRenderCount; - - /** - * Reset rendering scheduling. The next render request will be scheduled - * after a single delay unit. - */ - public void beginRenderScheduling() { - mPendingRenderCount = 0; - } - - /** - * Schedule rendering the given preview. Each successive call will add an additional - * delay unit to the schedule from the previous {@link #scheduleRender(RenderPreview)} - * call, until {@link #beginRenderScheduling()} is called again. - * - * @param preview the preview to render - */ - public void scheduleRender(@NonNull RenderPreview preview) { - mPendingRenderCount++; - preview.render(mPendingRenderCount * RENDER_DELAY); - } - - /** - * Switch to the given configuration preview - * - * @param preview the preview to switch to - */ - public void switchTo(@NonNull RenderPreview preview) { - IFile input = preview.getAlternateInput(); - if (input != null) { - IWorkbenchPartSite site = mCanvas.getEditorDelegate().getEditor().getSite(); - try { - // This switches to the given file, but the file might not have - // an identical configuration to what was shown in the preview. - // For example, while viewing a 10" layout-xlarge file, it might - // show a preview for a 5" version tied to the default layout. If - // you click on it, it will open the default layout file, but it might - // be using a different screen size; any of those that match the - // default layout, say a 3.8". - // - // Thus, we need to also perform a screen size sync first - Configuration configuration = preview.getConfiguration(); - boolean setSize = false; - if (configuration instanceof NestedConfiguration) { - NestedConfiguration nestedConfig = (NestedConfiguration) configuration; - setSize = nestedConfig.isOverridingDevice(); - if (configuration instanceof VaryingConfiguration) { - VaryingConfiguration c = (VaryingConfiguration) configuration; - setSize |= c.isAlternatingDevice(); - } - - if (setSize) { - ConfigurationChooser chooser = getChooser(); - IFile editedFile = chooser.getEditedFile(); - if (editedFile != null) { - chooser.syncToVariations(CFG_DEVICE|CFG_DEVICE_STATE, - editedFile, configuration, false, false); - } - } - } - - IDE.openEditor(site.getWorkbenchWindow().getActivePage(), input, - CommonXmlEditor.ID); - } catch (PartInitException e) { - AdtPlugin.log(e, null); - } - return; - } - - GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor(); - ConfigurationChooser chooser = editor.getConfigurationChooser(); - - Configuration originalConfiguration = chooser.getConfiguration(); - - // The new configuration is the configuration which will become the configuration - // in the layout editor's chooser - Configuration previewConfiguration = preview.getConfiguration(); - Configuration newConfiguration = previewConfiguration; - if (newConfiguration instanceof NestedConfiguration) { - // Should never use a complementing configuration for the main - // rendering's configuration; instead, create a new configuration - // with a snapshot of the configuration's current values - newConfiguration = Configuration.copy(previewConfiguration); - - // Remap all the previews to be parented to this new copy instead - // of the old one (which is no longer controlled by the chooser) - for (RenderPreview p : mPreviews) { - Configuration configuration = p.getConfiguration(); - if (configuration instanceof NestedConfiguration) { - NestedConfiguration nested = (NestedConfiguration) configuration; - nested.setParent(newConfiguration); - } - } - } - - // Make a preview for the configuration which *was* showing in the - // chooser up until this point: - RenderPreview newPreview = mCanvas.getPreview(); - if (newPreview == null) { - newPreview = RenderPreview.create(this, originalConfiguration); - } - - // Update its configuration such that it is complementing or inheriting - // from the new chosen configuration - if (previewConfiguration instanceof VaryingConfiguration) { - VaryingConfiguration varying = VaryingConfiguration.create( - (VaryingConfiguration) previewConfiguration, - newConfiguration); - varying.updateDisplayName(); - originalConfiguration = varying; - newPreview.setConfiguration(originalConfiguration); - } else if (previewConfiguration instanceof NestedConfiguration) { - NestedConfiguration nested = NestedConfiguration.create( - (NestedConfiguration) previewConfiguration, - originalConfiguration, - newConfiguration); - nested.setDisplayName(nested.computeDisplayName()); - originalConfiguration = nested; - newPreview.setConfiguration(originalConfiguration); - } - - // Replace clicked preview with preview of the formerly edited main configuration - // This doesn't work yet because the image overlay has had its image - // replaced by the configuration previews! I should make a list of them - //newPreview.setFullImage(mImageOverlay.getAwtImage()); - for (int i = 0, n = mPreviews.size(); i < n; i++) { - if (preview == mPreviews.get(i)) { - mPreviews.set(i, newPreview); - break; - } - } - - // Stash the corresponding preview (not active) on the canvas so we can - // retrieve it if clicking to some other preview later - mCanvas.setPreview(preview); - preview.setVisible(false); - - // Switch to the configuration from the clicked preview (though it's - // most likely a copy, see above) - chooser.setConfiguration(newConfiguration); - editor.changed(MASK_ALL); - - // Scroll to the top again, if necessary - mCanvas.getVerticalBar().setSelection(mCanvas.getVerticalBar().getMinimum()); - - mNeedLayout = mNeedZoom = true; - mCanvas.redraw(); - mAnimation = new SwapAnimation(preview, newPreview); - } - - /** - * Gets the preview at the given location, or null if none. This is - * currently deeply tied to where things are painted in onPaint(). - */ - RenderPreview getPreview(ControlPoint mousePos) { - if (hasPreviews()) { - int rootX = getX(); - if (mousePos.x < rootX) { - return null; - } - int rootY = getY(); - - for (RenderPreview preview : mPreviews) { - int x = rootX + preview.getX(); - int y = rootY + preview.getY(); - if (mousePos.x >= x && mousePos.x <= x + preview.getWidth()) { - if (mousePos.y >= y && mousePos.y <= y + preview.getHeight()) { - return preview; - } - } - } - } - - return null; - } - - private int getX() { - return mHScale.translate(0); - } - - private int getY() { - return mVScale.translate(0); - } - - private int getZoomX() { - Rectangle clientArea = mCanvas.getClientArea(); - int x = clientArea.x + clientArea.width - ZOOM_ICON_WIDTH; - if (x < mHScale.getScaledImgSize() + PREVIEW_HGAP) { - // No visible previews because the main image is zoomed too far - return -1; - } - - return x - 6; - } - - private int getZoomY() { - Rectangle clientArea = mCanvas.getClientArea(); - return clientArea.y + 5; - } - - /** - * Returns the height of the layout - * - * @return the height - */ - public int getHeight() { - return mLayoutHeight; - } - - /** - * Notifies that preview manager that the mouse cursor has moved to the - * given control position within the layout canvas - * - * @param mousePos the mouse position, relative to the layout canvas - */ - public void moved(ControlPoint mousePos) { - RenderPreview hovered = getPreview(mousePos); - if (hovered != mActivePreview) { - if (mActivePreview != null) { - mActivePreview.setActive(false); - } - mActivePreview = hovered; - if (mActivePreview != null) { - mActivePreview.setActive(true); - } - mCanvas.redraw(); - } - } - - /** - * Notifies that preview manager that the mouse cursor has entered the layout canvas - * - * @param mousePos the mouse position, relative to the layout canvas - */ - public void enter(ControlPoint mousePos) { - moved(mousePos); - } - - /** - * Notifies that preview manager that the mouse cursor has exited the layout canvas - * - * @param mousePos the mouse position, relative to the layout canvas - */ - public void exit(ControlPoint mousePos) { - if (mActivePreview != null) { - mActivePreview.setActive(false); - } - mActivePreview = null; - mCanvas.redraw(); - } - - /** - * Process a mouse click, and return true if it was handled by this manager - * (e.g. the click was on a preview) - * - * @param mousePos the mouse position where the click occurred - * @return true if the click occurred over a preview and was handled, false otherwise - */ - public boolean click(ControlPoint mousePos) { - // Clicked zoom? - int x = getZoomX(); - if (x > 0) { - if (mousePos.x >= x && mousePos.x <= x + ZOOM_ICON_WIDTH) { - int y = getZoomY(); - if (mousePos.y >= y && mousePos.y <= y + 4 * ZOOM_ICON_HEIGHT) { - if (mousePos.y < y + ZOOM_ICON_HEIGHT) { - zoomIn(); - } else if (mousePos.y < y + 2 * ZOOM_ICON_HEIGHT) { - zoomOut(); - } else if (mousePos.y < y + 3 * ZOOM_ICON_HEIGHT) { - zoomReset(); - } else { - selectMode(NONE); - } - return true; - } - } - } - - RenderPreview preview = getPreview(mousePos); - if (preview != null) { - boolean handled = preview.click(mousePos.x - getX() - preview.getX(), - mousePos.y - getY() - preview.getY()); - if (handled) { - // In case layout was performed, there could be a new preview - // under this coordinate now, so make sure it's hover etc - // shows up - moved(mousePos); - return true; - } - } - - return false; - } - - /** - * Returns true if there are thumbnail previews - * - * @return true if thumbnails are being shown - */ - public boolean hasPreviews() { - return mPreviews != null && !mPreviews.isEmpty(); - } - - - private void sortPreviewsByScreenSize() { - if (mPreviews != null) { - Collections.sort(mPreviews, new Comparator<RenderPreview>() { - @Override - public int compare(RenderPreview preview1, RenderPreview preview2) { - Configuration config1 = preview1.getConfiguration(); - Configuration config2 = preview2.getConfiguration(); - Device device1 = config1.getDevice(); - Device device2 = config1.getDevice(); - if (device1 != null && device2 != null) { - Screen screen1 = device1.getDefaultHardware().getScreen(); - Screen screen2 = device2.getDefaultHardware().getScreen(); - if (screen1 != null && screen2 != null) { - double delta = screen1.getDiagonalLength() - - screen2.getDiagonalLength(); - if (delta != 0.0) { - return (int) Math.signum(delta); - } else { - if (screen1.getPixelDensity() != screen2.getPixelDensity()) { - return screen1.getPixelDensity().compareTo( - screen2.getPixelDensity()); - } - } - } - - } - State state1 = config1.getDeviceState(); - State state2 = config2.getDeviceState(); - if (state1 != state2 && state1 != null && state2 != null) { - return state1.getName().compareTo(state2.getName()); - } - - return preview1.getDisplayName().compareTo(preview2.getDisplayName()); - } - }); - } - } - - private void sortPreviewsByOrientation() { - if (mPreviews != null) { - Collections.sort(mPreviews, new Comparator<RenderPreview>() { - @Override - public int compare(RenderPreview preview1, RenderPreview preview2) { - Configuration config1 = preview1.getConfiguration(); - Configuration config2 = preview2.getConfiguration(); - State state1 = config1.getDeviceState(); - State state2 = config2.getDeviceState(); - if (state1 != state2 && state1 != null && state2 != null) { - return state1.getName().compareTo(state2.getName()); - } - - return preview1.getDisplayName().compareTo(preview2.getDisplayName()); - } - }); - } - } - - /** - * Vertical scrollbar listener which updates render previews which are not - * visible and triggers a redraw - */ - private class ScrollBarListener implements SelectionListener { - @Override - public void widgetSelected(SelectionEvent e) { - if (mPreviews == null) { - return; - } - - ScrollBar bar = mCanvas.getVerticalBar(); - int selection = bar.getSelection(); - int thumb = bar.getThumb(); - int maxY = selection + thumb; - beginRenderScheduling(); - for (RenderPreview preview : mPreviews) { - if (!preview.isVisible() && preview.getY() <= maxY) { - preview.setVisible(true); - } - } - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - } - - /** Animation overlay shown briefly after swapping two previews */ - private class SwapAnimation implements Runnable { - private long begin; - private long end; - private static final long DURATION = 400; // ms - private Rect initialRect1; - private Rect targetRect1; - private Rect initialRect2; - private Rect targetRect2; - private RenderPreview preview; - - SwapAnimation(RenderPreview preview1, RenderPreview preview2) { - begin = System.currentTimeMillis(); - end = begin + DURATION; - - initialRect1 = new Rect(preview1.getX(), preview1.getY(), - preview1.getWidth(), preview1.getHeight()); - - CanvasTransform hi = mCanvas.getHorizontalTransform(); - CanvasTransform vi = mCanvas.getVerticalTransform(); - initialRect2 = new Rect(hi.translate(0), vi.translate(0), - hi.getScaledImgSize(), vi.getScaledImgSize()); - preview = preview2; - } - - void tick(GC gc) { - long now = System.currentTimeMillis(); - if (now > end || mCanvas.isDisposed()) { - mAnimation = null; - return; - } - - CanvasTransform hi = mCanvas.getHorizontalTransform(); - CanvasTransform vi = mCanvas.getVerticalTransform(); - if (targetRect1 == null) { - targetRect1 = new Rect(hi.translate(0), vi.translate(0), - hi.getScaledImgSize(), vi.getScaledImgSize()); - } - double portion = (now - begin) / (double) DURATION; - Rect rect1 = new Rect( - (int) (portion * (targetRect1.x - initialRect1.x) + initialRect1.x), - (int) (portion * (targetRect1.y - initialRect1.y) + initialRect1.y), - (int) (portion * (targetRect1.w - initialRect1.w) + initialRect1.w), - (int) (portion * (targetRect1.h - initialRect1.h) + initialRect1.h)); - - if (targetRect2 == null) { - targetRect2 = new Rect(preview.getX(), preview.getY(), - preview.getWidth(), preview.getHeight()); - } - portion = (now - begin) / (double) DURATION; - Rect rect2 = new Rect( - (int) (portion * (targetRect2.x - initialRect2.x) + initialRect2.x), - (int) (portion * (targetRect2.y - initialRect2.y) + initialRect2.y), - (int) (portion * (targetRect2.w - initialRect2.w) + initialRect2.w), - (int) (portion * (targetRect2.h - initialRect2.h) + initialRect2.h)); - - gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY)); - gc.drawRectangle(rect1.x, rect1.y, rect1.w, rect1.h); - gc.drawRectangle(rect2.x, rect2.y, rect2.w, rect2.h); - - mCanvas.getDisplay().timerExec(5, this); - } - - @Override - public void run() { - mCanvas.redraw(); - } - } - - /** - * Notifies the {@linkplain RenderPreviewManager} that the configuration used - * in the main chooser has been changed. This may require updating parent references - * in the preview configurations inheriting from it. - * - * @param oldConfiguration the previous configuration - * @param newConfiguration the new configuration in the chooser - */ - public void updateChooserConfig( - @NonNull Configuration oldConfiguration, - @NonNull Configuration newConfiguration) { - if (hasPreviews()) { - for (RenderPreview preview : mPreviews) { - Configuration configuration = preview.getConfiguration(); - if (configuration instanceof NestedConfiguration) { - NestedConfiguration nestedConfig = (NestedConfiguration) configuration; - if (nestedConfig.getParent() == oldConfiguration) { - nestedConfig.setParent(newConfiguration); - } - } - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java deleted file mode 100644 index 0f06d7f8a..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -/** - * The {@linkplain RenderPreviewMode} records what type of configurations to - * render in the layout editor - */ -public enum RenderPreviewMode { - /** Generate a set of default previews with maximum variation */ - DEFAULT, - - /** Preview all the locales */ - LOCALES, - - /** Preview all the screen sizes */ - SCREENS, - - /** Preview layout as included in other layouts */ - INCLUDES, - - /** Preview all the variations of this layout */ - VARIATIONS, - - /** Show a manually configured set of previews */ - CUSTOM, - - /** No previews */ - NONE; -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java deleted file mode 100644 index 3b9e2fc0f..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java +++ /dev/null @@ -1,668 +0,0 @@ -/* - * 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.SdkConstants.LAYOUT_RESOURCE_PREFIX; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.IClientRulesEngine; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Rect; -import com.android.ide.common.rendering.HardwareConfigHelper; -import com.android.ide.common.rendering.LayoutLibrary; -import com.android.ide.common.rendering.RenderSecurityManager; -import com.android.ide.common.rendering.api.AssetRepository; -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.common.rendering.api.DrawableParams; -import com.android.ide.common.rendering.api.HardwareConfig; -import com.android.ide.common.rendering.api.IImageFactory; -import com.android.ide.common.rendering.api.ILayoutPullParser; -import com.android.ide.common.rendering.api.LayoutLog; -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.SessionParams; -import com.android.ide.common.rendering.api.SessionParams.RenderingMode; -import com.android.ide.common.rendering.api.ViewInfo; -import com.android.ide.common.resources.ResourceResolver; -import com.android.ide.common.resources.configuration.FolderConfiguration; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.layout.ContextPullParser; -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.Configuration; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Locale; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; -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.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo.ActivityAttributes; -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 com.android.ide.eclipse.adt.internal.sdk.Sdk; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.devices.Device; -import com.google.common.base.Charsets; -import com.google.common.io.Files; - -import org.eclipse.core.resources.IProject; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.awt.Toolkit; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.StringReader; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * The {@link RenderService} provides rendering and layout information for - * Android layouts. This is a wrapper around the layout library. - */ -public class RenderService { - private static final Object RENDERING_LOCK = new Object(); - - /** Reference to the file being edited. Can also be used to access the {@link IProject}. */ - private final GraphicalEditorPart mEditor; - - // The following fields are inferred from the editor and not customizable by the - // client of the render service: - - private final IProject mProject; - private final ProjectCallback mProjectCallback; - private final ResourceResolver mResourceResolver; - private final int mMinSdkVersion; - private final int mTargetSdkVersion; - private final LayoutLibrary mLayoutLib; - private final IImageFactory mImageFactory; - private final HardwareConfigHelper mHardwareConfigHelper; - private final Locale mLocale; - - // The following fields are optional or configurable using the various chained - // setters: - - private UiDocumentNode mModel; - private Reference mIncludedWithin; - private RenderingMode mRenderingMode = RenderingMode.NORMAL; - private LayoutLog mLogger; - private Integer mOverrideBgColor; - private boolean mShowDecorations = true; - private Set<UiElementNode> mExpandNodes = Collections.<UiElementNode>emptySet(); - private final Object mCredential; - - /** Use the {@link #create} factory instead */ - private RenderService(GraphicalEditorPart editor, Object credential) { - mEditor = editor; - mCredential = credential; - - mProject = editor.getProject(); - LayoutCanvas canvas = editor.getCanvasControl(); - mImageFactory = canvas.getImageOverlay(); - ConfigurationChooser chooser = editor.getConfigurationChooser(); - Configuration config = chooser.getConfiguration(); - FolderConfiguration folderConfig = config.getFullConfig(); - - Device device = config.getDevice(); - assert device != null; // Should only attempt render with configuration that has device - mHardwareConfigHelper = new HardwareConfigHelper(device); - mHardwareConfigHelper.setOrientation( - folderConfig.getScreenOrientationQualifier().getValue()); - - mLayoutLib = editor.getReadyLayoutLib(true /*displayError*/); - mResourceResolver = editor.getResourceResolver(); - mProjectCallback = editor.getProjectCallback(true /*reset*/, mLayoutLib); - mMinSdkVersion = editor.getMinSdkVersion(); - mTargetSdkVersion = editor.getTargetSdkVersion(); - mLocale = config.getLocale(); - } - - private RenderService(GraphicalEditorPart editor, - Configuration configuration, ResourceResolver resourceResolver, - Object credential) { - mEditor = editor; - mCredential = credential; - - mProject = editor.getProject(); - LayoutCanvas canvas = editor.getCanvasControl(); - mImageFactory = canvas.getImageOverlay(); - FolderConfiguration folderConfig = configuration.getFullConfig(); - - Device device = configuration.getDevice(); - assert device != null; - mHardwareConfigHelper = new HardwareConfigHelper(device); - mHardwareConfigHelper.setOrientation( - folderConfig.getScreenOrientationQualifier().getValue()); - - mLayoutLib = editor.getReadyLayoutLib(true /*displayError*/); - mResourceResolver = resourceResolver != null ? resourceResolver : editor.getResourceResolver(); - mProjectCallback = editor.getProjectCallback(true /*reset*/, mLayoutLib); - mMinSdkVersion = editor.getMinSdkVersion(); - mTargetSdkVersion = editor.getTargetSdkVersion(); - mLocale = configuration.getLocale(); - } - - private RenderSecurityManager createSecurityManager() { - String projectPath = null; - String sdkPath = null; - if (RenderSecurityManager.RESTRICT_READS) { - projectPath = AdtUtils.getAbsolutePath(mProject).toFile().getPath(); - Sdk sdk = Sdk.getCurrent(); - sdkPath = sdk != null ? sdk.getSdkOsLocation() : null; - } - RenderSecurityManager securityManager = new RenderSecurityManager(sdkPath, projectPath); - securityManager.setLogger(AdtPlugin.getDefault()); - - // Make sure this is initialized before we attempt to use it from layoutlib - Toolkit.getDefaultToolkit(); - - return securityManager; - } - - /** - * Returns true if this configuration supports the given rendering - * capability - * - * @param target the target to look up the layout library for - * @param capability the capability to check - * @return true if the capability is supported - */ - public static boolean supports( - @NonNull IAndroidTarget target, - @NonNull Capability capability) { - Sdk sdk = Sdk.getCurrent(); - if (sdk != null) { - AndroidTargetData targetData = sdk.getTargetData(target); - if (targetData != null) { - LayoutLibrary layoutLib = targetData.getLayoutLibrary(); - if (layoutLib != null) { - return layoutLib.supports(capability); - } - } - } - - return false; - } - - /** - * Creates a new {@link RenderService} associated with the given editor. - * - * @param editor the editor to provide configuration data such as the render target - * @return a {@link RenderService} which can perform rendering services - */ - public static RenderService create(GraphicalEditorPart editor) { - // Delegate to editor such that it can pass its credential to the service - return editor.createRenderService(); - } - - /** - * Creates a new {@link RenderService} associated with the given editor. - * - * @param editor the editor to provide configuration data such as the render target - * @param credential the sandbox credential - * @return a {@link RenderService} which can perform rendering services - */ - @NonNull - public static RenderService create(GraphicalEditorPart editor, Object credential) { - return new RenderService(editor, credential); - } - - /** - * Creates a new {@link RenderService} associated with the given editor. - * - * @param editor the editor to provide configuration data such as the render target - * @param configuration the configuration to use (and fallback to editor for the rest) - * @param resolver a resource resolver to use to look up resources - * @return a {@link RenderService} which can perform rendering services - */ - public static RenderService create(GraphicalEditorPart editor, - Configuration configuration, ResourceResolver resolver) { - // Delegate to editor such that it can pass its credential to the service - return editor.createRenderService(configuration, resolver); - } - - /** - * Creates a new {@link RenderService} associated with the given editor. - * - * @param editor the editor to provide configuration data such as the render target - * @param configuration the configuration to use (and fallback to editor for the rest) - * @param resolver a resource resolver to use to look up resources - * @param credential the sandbox credential - * @return a {@link RenderService} which can perform rendering services - */ - public static RenderService create(GraphicalEditorPart editor, - Configuration configuration, ResourceResolver resolver, Object credential) { - return new RenderService(editor, configuration, resolver, credential); - } - - /** - * Renders the given model, using this editor's theme and screen settings, and returns - * the result as a {@link RenderSession}. - * - * @param model the model to be rendered, which can be different than the editor's own - * {@link #getModel()}. - * @param width the width to use for the layout, or -1 to use the width of the screen - * associated with this editor - * @param height the height to use for the layout, or -1 to use the height of the screen - * associated with this editor - * @param explodeNodes a set of nodes to explode, or null for none - * @param overrideBgColor If non-null, use the given color as a background to render over - * rather than the normal background requested by the theme - * @param noDecor If true, don't draw window decorations like the system bar - * @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} - */ - - /** - * Sets the {@link LayoutLog} to be used during rendering. If none is specified, a - * silent logger will be used. - * - * @param logger the log to be used - * @return this (such that chains of setters can be stringed together) - */ - public RenderService setLog(LayoutLog logger) { - mLogger = logger; - return this; - } - - /** - * Sets the model to be rendered, which can be different than the editor's own - * {@link GraphicalEditorPart#getModel()}. - * - * @param model the model to be rendered - * @return this (such that chains of setters can be stringed together) - */ - public RenderService setModel(UiDocumentNode model) { - mModel = model; - return this; - } - - /** - * Overrides the width and height to be used during rendering (which might be adjusted if - * the {@link #setRenderingMode(RenderingMode)} is {@link RenderingMode#FULL_EXPAND}. - * - * A value of -1 will make the rendering use the normal width and height coming from the - * {@link Configuration#getDevice()} object. - * - * @param overrideRenderWidth the width in pixels of the layout to be rendered - * @param overrideRenderHeight the height in pixels of the layout to be rendered - * @return this (such that chains of setters can be stringed together) - */ - public RenderService setOverrideRenderSize(int overrideRenderWidth, int overrideRenderHeight) { - mHardwareConfigHelper.setOverrideRenderSize(overrideRenderWidth, overrideRenderHeight); - return this; - } - - /** - * Sets the max width and height to be used during rendering (which might be adjusted if - * the {@link #setRenderingMode(RenderingMode)} is {@link RenderingMode#FULL_EXPAND}. - * - * A value of -1 will make the rendering use the normal width and height coming from the - * {@link Configuration#getDevice()} object. - * - * @param maxRenderWidth the max width in pixels of the layout to be rendered - * @param maxRenderHeight the max height in pixels of the layout to be rendered - * @return this (such that chains of setters can be stringed together) - */ - public RenderService setMaxRenderSize(int maxRenderWidth, int maxRenderHeight) { - mHardwareConfigHelper.setMaxRenderSize(maxRenderWidth, maxRenderHeight); - return this; - } - - /** - * Sets the {@link RenderingMode} to be used during rendering. If none is specified, - * the default is {@link RenderingMode#NORMAL}. - * - * @param renderingMode the rendering mode to be used - * @return this (such that chains of setters can be stringed together) - */ - public RenderService setRenderingMode(RenderingMode renderingMode) { - mRenderingMode = renderingMode; - return this; - } - - /** - * Sets the overriding background color to be used, if any. The color should be a - * bitmask of AARRGGBB. The default is null. - * - * @param overrideBgColor the overriding background color to be used in the rendering, - * in the form of a AARRGGBB bitmask, or null to use no custom background. - * @return this (such that chains of setters can be stringed together) - */ - public RenderService setOverrideBgColor(Integer overrideBgColor) { - mOverrideBgColor = overrideBgColor; - return this; - } - - /** - * Sets whether the rendering should include decorations such as a system bar, an - * application bar etc depending on the SDK target and theme. The default is true. - * - * @param showDecorations true if the rendering should include system bars etc. - * @return this (such that chains of setters can be stringed together) - */ - public RenderService setDecorations(boolean showDecorations) { - mShowDecorations = showDecorations; - return this; - } - - /** - * Sets the nodes to expand during rendering. These will be padded with approximately - * 20 pixels and also highlighted by the {@link EmptyViewsOverlay}. The default is an - * empty collection. - * - * @param nodesToExpand the nodes to be expanded - * @return this (such that chains of setters can be stringed together) - */ - public RenderService setNodesToExpand(Set<UiElementNode> nodesToExpand) { - mExpandNodes = nodesToExpand; - return this; - } - - /** - * Sets the {@link Reference} to an outer layout that this layout should be rendered - * within. The outer layout <b>must</b> contain an include tag which points to this - * layout. The default is null. - * - * @param includedWithin a reference to an outer layout to render this layout within - * @return this (such that chains of setters can be stringed together) - */ - public RenderService setIncludedWithin(Reference includedWithin) { - mIncludedWithin = includedWithin; - return this; - } - - /** Initializes any remaining optional fields after all setters have been called */ - private void finishConfiguration() { - if (mLogger == null) { - // Silent logging - mLogger = new LayoutLog(); - } - } - - /** - * Renders the model and returns the result as a {@link RenderSession}. - * @return the {@link RenderSession} resulting from rendering the current model - */ - public RenderSession createRenderSession() { - assert mModel != null : "Incomplete service config"; - finishConfiguration(); - - if (mResourceResolver == null) { - // Abort the rendering if the resources are not found. - return null; - } - - HardwareConfig hardwareConfig = mHardwareConfigHelper.getConfig(); - - UiElementPullParser modelParser = new UiElementPullParser(mModel, - false, mExpandNodes, hardwareConfig.getDensity(), mProject); - ILayoutPullParser topParser = modelParser; - - // Code to support editing included layout - // first reset the layout parser just in case. - mProjectCallback.setLayoutParser(null, null); - - if (mIncludedWithin != null) { - // Outer layout name: - String contextLayoutName = mIncludedWithin.getName(); - - // Find the layout file. - ResourceValue contextLayout = mResourceResolver.findResValue( - LAYOUT_RESOURCE_PREFIX + contextLayoutName, false /* forceFrameworkOnly*/); - if (contextLayout != null) { - File layoutFile = new File(contextLayout.getValue()); - if (layoutFile.isFile()) { - try { - // Get the name of the layout actually being edited, without the extension - // as it's what IXmlPullParser.getParser(String) will receive. - String queryLayoutName = mEditor.getLayoutResourceName(); - mProjectCallback.setLayoutParser(queryLayoutName, modelParser); - topParser = new ContextPullParser(mProjectCallback, layoutFile); - topParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - String xmlText = Files.toString(layoutFile, Charsets.UTF_8); - topParser.setInput(new StringReader(xmlText)); - } catch (IOException e) { - AdtPlugin.log(e, null); - } catch (XmlPullParserException e) { - AdtPlugin.log(e, null); - } - } - } - } - - SessionParams params = new SessionParams( - topParser, - mRenderingMode, - mProject /* projectKey */, - hardwareConfig, - mResourceResolver, - mProjectCallback, - mMinSdkVersion, - mTargetSdkVersion, - mLogger); - - // Request margin and baseline information. - // TODO: Be smarter about setting this; start without it, and on the first request - // for an extended view info, re-render in the same session, and then set a flag - // which will cause this to create extended view info each time from then on in the - // same session - params.setExtendedViewInfoMode(true); - - params.setLocale(mLocale.toLocaleId()); - params.setAssetRepository(new AssetRepository()); - - ManifestInfo manifestInfo = ManifestInfo.get(mProject); - try { - params.setRtlSupport(manifestInfo.isRtlSupported()); - } catch (Exception e) { - // ignore. - } - if (!mShowDecorations) { - params.setForceNoDecor(); - } else { - try { - params.setAppLabel(manifestInfo.getApplicationLabel()); - params.setAppIcon(manifestInfo.getApplicationIcon()); - String activity = mEditor.getConfigurationChooser().getConfiguration().getActivity(); - if (activity != null) { - ActivityAttributes info = manifestInfo.getActivityAttributes(activity); - if (info != null) { - if (info.getLabel() != null) { - params.setAppLabel(info.getLabel()); - } - if (info.getIcon() != null) { - params.setAppIcon(info.getIcon()); - } - } - } - } catch (Exception e) { - // ignore. - } - } - - if (mOverrideBgColor != null) { - params.setOverrideBgColor(mOverrideBgColor.intValue()); - } - - // set the Image Overlay as the image factory. - params.setImageFactory(mImageFactory); - - mProjectCallback.setLogger(mLogger); - mProjectCallback.setResourceResolver(mResourceResolver); - RenderSecurityManager securityManager = createSecurityManager(); - try { - securityManager.setActive(true, mCredential); - synchronized (RENDERING_LOCK) { - return mLayoutLib.createSession(params); - } - } catch (RuntimeException t) { - // Exceptions from the bridge - mLogger.error(null, t.getLocalizedMessage(), t, null); - throw t; - } finally { - securityManager.dispose(mCredential); - mProjectCallback.setLogger(null); - mProjectCallback.setResourceResolver(null); - } - } - - /** - * Renders the given resource value (which should refer to a drawable) and returns it - * as an image - * - * @param drawableResourceValue the drawable resource value to be rendered, or null - * @return the image, or null if something went wrong - */ - public BufferedImage renderDrawable(ResourceValue drawableResourceValue) { - if (drawableResourceValue == null) { - return null; - } - - finishConfiguration(); - - HardwareConfig hardwareConfig = mHardwareConfigHelper.getConfig(); - - DrawableParams params = new DrawableParams(drawableResourceValue, mProject, hardwareConfig, - mResourceResolver, mProjectCallback, mMinSdkVersion, - mTargetSdkVersion, mLogger); - params.setAssetRepository(new AssetRepository()); - params.setForceNoDecor(); - Result result = mLayoutLib.renderDrawable(params); - if (result != null && result.isSuccess()) { - Object data = result.getData(); - if (data instanceof BufferedImage) { - return (BufferedImage) data; - } - } - - return null; - } - - /** - * Measure the children of the given parent node, applying the given filter to the - * pull parser's attribute values. - * - * @param parent the parent node to measure children for - * @param filter the filter to apply to the attribute values - * @return a map from node children of the parent to new bounds of the nodes - */ - public Map<INode, Rect> measureChildren(INode parent, - final IClientRulesEngine.AttributeFilter filter) { - finishConfiguration(); - HardwareConfig hardwareConfig = mHardwareConfigHelper.getConfig(); - - final NodeFactory mNodeFactory = mEditor.getCanvasControl().getNodeFactory(); - UiElementNode parentNode = ((NodeProxy) parent).getNode(); - UiElementPullParser topParser = new UiElementPullParser(parentNode, - false, Collections.<UiElementNode>emptySet(), hardwareConfig.getDensity(), - mProject) { - @Override - public String getAttributeValue(String namespace, String localName) { - if (filter != null) { - Object cookie = getViewCookie(); - if (cookie instanceof UiViewElementNode) { - NodeProxy node = mNodeFactory.create((UiViewElementNode) cookie); - if (node != null) { - String value = filter.getAttribute(node, namespace, localName); - if (value != null) { - return value; - } - // null means no preference, not "unset". - } - } - } - - return super.getAttributeValue(namespace, localName); - } - - /** - * The parser usually assumes that the top level node is a document node that - * should be skipped, and that's not the case when we render in the middle of - * the tree, so override {@link UiElementPullParser#onNextFromStartDocument} - * to change this behavior - */ - @Override - public void onNextFromStartDocument() { - mParsingState = START_TAG; - } - }; - - SessionParams params = new SessionParams( - topParser, - RenderingMode.FULL_EXPAND, - mProject /* projectKey */, - hardwareConfig, - mResourceResolver, - mProjectCallback, - mMinSdkVersion, - mTargetSdkVersion, - mLogger); - params.setLayoutOnly(); - params.setForceNoDecor(); - params.setAssetRepository(new AssetRepository()); - - RenderSession session = null; - mProjectCallback.setLogger(mLogger); - mProjectCallback.setResourceResolver(mResourceResolver); - RenderSecurityManager securityManager = createSecurityManager(); - try { - securityManager.setActive(true, mCredential); - synchronized (RENDERING_LOCK) { - session = mLayoutLib.createSession(params); - } - if (session.getResult().isSuccess()) { - assert session.getRootViews().size() == 1; - ViewInfo root = session.getRootViews().get(0); - List<ViewInfo> children = root.getChildren(); - Map<INode, Rect> map = new HashMap<INode, Rect>(children.size()); - for (ViewInfo info : children) { - if (info.getCookie() instanceof UiViewElementNode) { - UiViewElementNode uiNode = (UiViewElementNode) info.getCookie(); - NodeProxy node = mNodeFactory.create(uiNode); - map.put(node, new Rect(info.getLeft(), info.getTop(), - info.getRight() - info.getLeft(), - info.getBottom() - info.getTop())); - } - } - - return map; - } - } catch (RuntimeException t) { - // Exceptions from the bridge - mLogger.error(null, t.getLocalizedMessage(), t, null); - throw t; - } finally { - securityManager.dispose(mCredential); - mProjectCallback.setLogger(null); - mProjectCallback.setResourceResolver(null); - if (session != null) { - session.dispose(); - } - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ResizeGesture.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ResizeGesture.java deleted file mode 100644 index 4d51c07de..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ResizeGesture.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 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.common.api.DropFeedback; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.ResizePolicy; -import com.android.ide.common.api.SegmentType; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.Position; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; -import com.android.utils.Pair; - -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.graphics.GC; - -import java.util.Collections; -import java.util.List; - -/** - * A {@link ResizeGesture} is a gesture for resizing a selected widget. It is initiated - * by a drag of a {@link SelectionHandle}. - */ -public class ResizeGesture extends Gesture { - /** The {@link Overlay} drawn for the gesture feedback. */ - private ResizeOverlay mOverlay; - - /** The canvas associated with this gesture. */ - private LayoutCanvas mCanvas; - - /** The selection handle we're dragging to perform this resize */ - private SelectionHandle mHandle; - - private NodeProxy mParentNode; - private NodeProxy mChildNode; - private DropFeedback mFeedback; - private ResizePolicy mResizePolicy; - private SegmentType mHorizontalEdge; - private SegmentType mVerticalEdge; - - /** - * Creates a new marquee selection (selection swiping). - * - * @param canvas The canvas where selection is performed. - * @param item The selected item the handle corresponds to - * @param handle The handle being dragged to perform the resize - */ - public ResizeGesture(LayoutCanvas canvas, SelectionItem item, SelectionHandle handle) { - mCanvas = canvas; - mHandle = handle; - - mChildNode = item.getNode(); - mParentNode = (NodeProxy) mChildNode.getParent(); - mResizePolicy = item.getResizePolicy(); - mHorizontalEdge = getHorizontalEdgeType(mHandle); - mVerticalEdge = getVerticalEdgeType(mHandle); - } - - @Override - public void begin(ControlPoint pos, int startMask) { - super.begin(pos, startMask); - - mCanvas.getSelectionOverlay().setHidden(true); - - RulesEngine rulesEngine = mCanvas.getRulesEngine(); - Rect newBounds = getNewBounds(pos); - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - CanvasViewInfo childInfo = viewHierarchy.findViewInfoFor(mChildNode); - CanvasViewInfo parentInfo = viewHierarchy.findViewInfoFor(mParentNode); - Object childView = childInfo != null ? childInfo.getViewObject() : null; - Object parentView = parentInfo != null ? parentInfo.getViewObject() : null; - mFeedback = rulesEngine.callOnResizeBegin(mChildNode, mParentNode, newBounds, - mHorizontalEdge, mVerticalEdge, childView, parentView); - update(pos); - mCanvas.getGestureManager().updateMessage(mFeedback); - } - - @Override - public boolean keyPressed(KeyEvent event) { - update(mCanvas.getGestureManager().getCurrentControlPoint()); - mCanvas.redraw(); - return true; - } - - @Override - public boolean keyReleased(KeyEvent event) { - update(mCanvas.getGestureManager().getCurrentControlPoint()); - mCanvas.redraw(); - return true; - } - - @Override - public void update(ControlPoint pos) { - super.update(pos); - RulesEngine rulesEngine = mCanvas.getRulesEngine(); - Rect newBounds = getNewBounds(pos); - int modifierMask = mCanvas.getGestureManager().getRuleModifierMask(); - rulesEngine.callOnResizeUpdate(mFeedback, mChildNode, mParentNode, newBounds, - modifierMask); - mCanvas.getGestureManager().updateMessage(mFeedback); - } - - @Override - public void end(ControlPoint pos, boolean canceled) { - super.end(pos, canceled); - - if (!canceled) { - RulesEngine rulesEngine = mCanvas.getRulesEngine(); - Rect newBounds = getNewBounds(pos); - rulesEngine.callOnResizeEnd(mFeedback, mChildNode, mParentNode, newBounds); - } - - mCanvas.getSelectionOverlay().setHidden(false); - } - - @Override - public Pair<Boolean, Boolean> getTooltipPosition() { - return Pair.of(mHorizontalEdge != SegmentType.TOP, mVerticalEdge != SegmentType.LEFT); - } - - /** - * For the new mouse position, compute the resized bounds (the bounding rectangle that - * the view should be resized to). This is not just a width or height, since in some - * cases resizing will change the x/y position of the view as well (for example, in - * RelativeLayout or in AbsoluteLayout). - */ - private Rect getNewBounds(ControlPoint pos) { - LayoutPoint p = pos.toLayout(); - LayoutPoint start = mStart.toLayout(); - Rect b = mChildNode.getBounds(); - Position direction = mHandle.getPosition(); - - int x = b.x; - int y = b.y; - int w = b.w; - int h = b.h; - int deltaX = p.x - start.x; - int deltaY = p.y - start.y; - - if (deltaX == 0 && deltaY == 0) { - // No move - just use the existing bounds - return b; - } - - if (mResizePolicy.isAspectPreserving() && w != 0 && h != 0) { - double aspectRatio = w / (double) h; - int newW = Math.abs(b.w + (direction.isLeft() ? -deltaX : deltaX)); - int newH = Math.abs(b.h + (direction.isTop() ? -deltaY : deltaY)); - double newAspectRatio = newW / (double) newH; - if (newH == 0 || newAspectRatio > aspectRatio) { - deltaY = (int) (deltaX / aspectRatio); - } else { - deltaX = (int) (deltaY * aspectRatio); - } - } - if (direction.isLeft()) { - // The user is dragging the left edge, so the position is anchored on the - // right. - int x2 = b.x + b.w; - int nx1 = b.x + deltaX; - if (nx1 <= x2) { - x = nx1; - w = x2 - x; - } else { - w = 0; - x = x2; - } - } else if (direction.isRight()) { - // The user is dragging the right edge, so the position is anchored on the - // left. - int nx2 = b.x + b.w + deltaX; - if (nx2 >= b.x) { - w = nx2 - b.x; - } else { - w = 0; - } - } else { - assert direction == Position.BOTTOM_MIDDLE || direction == Position.TOP_MIDDLE; - } - - if (direction.isTop()) { - // The user is dragging the top edge, so the position is anchored on the - // bottom. - int y2 = b.y + b.h; - int ny1 = b.y + deltaY; - if (ny1 < y2) { - y = ny1; - h = y2 - y; - } else { - h = 0; - y = y2; - } - } else if (direction.isBottom()) { - // The user is dragging the bottom edge, so the position is anchored on the - // top. - int ny2 = b.y + b.h + deltaY; - if (ny2 >= b.y) { - h = ny2 - b.y; - } else { - h = 0; - } - } else { - assert direction == Position.LEFT_MIDDLE || direction == Position.RIGHT_MIDDLE; - } - - return new Rect(x, y, w, h); - } - - private static SegmentType getHorizontalEdgeType(SelectionHandle handle) { - switch (handle.getPosition()) { - case BOTTOM_LEFT: - case BOTTOM_RIGHT: - case BOTTOM_MIDDLE: - return SegmentType.BOTTOM; - case LEFT_MIDDLE: - case RIGHT_MIDDLE: - return null; - case TOP_LEFT: - case TOP_MIDDLE: - case TOP_RIGHT: - return SegmentType.TOP; - default: assert false : handle.getPosition(); - } - return null; - } - - private static SegmentType getVerticalEdgeType(SelectionHandle handle) { - switch (handle.getPosition()) { - case TOP_LEFT: - case LEFT_MIDDLE: - case BOTTOM_LEFT: - return SegmentType.LEFT; - case BOTTOM_MIDDLE: - case TOP_MIDDLE: - return null; - case TOP_RIGHT: - case RIGHT_MIDDLE: - case BOTTOM_RIGHT: - return SegmentType.RIGHT; - default: assert false : handle.getPosition(); - } - return null; - } - - - @Override - public List<Overlay> createOverlays() { - mOverlay = new ResizeOverlay(); - return Collections.<Overlay> singletonList(mOverlay); - } - - /** - * An {@link Overlay} to paint the resize feedback. This just delegates to the - * layout rule for the parent which is handling the resizing. - */ - private class ResizeOverlay extends Overlay { - @Override - public void paint(GC gc) { - if (mChildNode != null && mFeedback != null) { - RulesEngine rulesEngine = mCanvas.getRulesEngine(); - rulesEngine.callDropFeedbackPaint(mCanvas.getGcWrapper(), mChildNode, mFeedback); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionHandle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionHandle.java deleted file mode 100644 index c2db2431c..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionHandle.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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; - -/** - * A selection handle is a small rectangle on the border of a selected view which lets you - * change the size of the view by dragging it. - */ -public class SelectionHandle { - /** - * Size of the selection handle radius, in control coordinates. Note that this isn't - * necessarily a <b>circular</b> radius; in the case of a rectangular handle, the - * width and the height are both equal to this radius. - * Note also that this radius is in <b>control</b> coordinates, whereas the rest - * of the class operates in layout coordinates. This is because we do not want the - * selection handles to grow or shrink along with the screen zoom; they are always - * at the given pixel size in the control. - */ - public final static int PIXEL_RADIUS = 3; - - /** - * Extra number of pixels to look beyond the actual radius of the selection handle - * when matching mouse positions to handles - */ - public final static int PIXEL_MARGIN = 2; - - /** The position of the handle in the selection rectangle */ - enum Position { - TOP_MIDDLE(SWT.CURSOR_SIZEN), - TOP_RIGHT(SWT.CURSOR_SIZENE), - RIGHT_MIDDLE(SWT.CURSOR_SIZEE), - BOTTOM_RIGHT(SWT.CURSOR_SIZESE), - BOTTOM_MIDDLE(SWT.CURSOR_SIZES), - BOTTOM_LEFT(SWT.CURSOR_SIZESW), - LEFT_MIDDLE(SWT.CURSOR_SIZEW), - TOP_LEFT(SWT.CURSOR_SIZENW); - - /** Corresponding SWT cursor value */ - private int mSwtCursor; - - private Position(int swtCursor) { - mSwtCursor = swtCursor; - } - - private int getCursorType() { - return mSwtCursor; - } - - /** Is the {@link SelectionHandle} somewhere on the left edge? */ - boolean isLeft() { - return this == TOP_LEFT || this == LEFT_MIDDLE || this == BOTTOM_LEFT; - } - - /** Is the {@link SelectionHandle} somewhere on the right edge? */ - boolean isRight() { - return this == TOP_RIGHT || this == RIGHT_MIDDLE || this == BOTTOM_RIGHT; - } - - /** Is the {@link SelectionHandle} somewhere on the top edge? */ - boolean isTop() { - return this == TOP_LEFT || this == TOP_MIDDLE || this == TOP_RIGHT; - } - - /** Is the {@link SelectionHandle} somewhere on the bottom edge? */ - boolean isBottom() { - return this == BOTTOM_LEFT || this == BOTTOM_MIDDLE || this == BOTTOM_RIGHT; - } - }; - - /** The x coordinate of the center of the selection handle */ - public final int centerX; - /** The y coordinate of the center of the selection handle */ - public final int centerY; - /** The position of the handle in the selection rectangle */ - private final Position mPosition; - - /** - * Constructs a new {@link SelectionHandle} at the given layout coordinate - * corresponding to a handle at the given {@link Position}. - * - * @param centerX the x coordinate of the center of the selection handle - * @param centerY y coordinate of the center of the selection handle - * @param position the position of the handle in the selection rectangle - */ - public SelectionHandle(int centerX, int centerY, Position position) { - mPosition = position; - this.centerX = centerX; - this.centerY = centerY; - } - - /** - * Determines whether the given {@link LayoutPoint} is within the given distance in - * layout coordinates. The distance should incorporate at least the equivalent - * distance to the control coordinate space {@link #PIXEL_RADIUS}, but usually with a - * few extra pixels added in to make the corners easier to target. - * - * @param point the mouse position in layout coordinates - * @param distance the distance from the center of the handle to check whether the - * point fits within - * @return true if the given point is within the given distance of this handle - */ - public boolean contains(LayoutPoint point, int distance) { - return (point.x >= centerX - distance - && point.x <= centerX + distance - && point.y >= centerY - distance - && point.y <= centerY + distance); - } - - /** - * Returns the position of the handle in the selection rectangle - * - * @return the position of the handle in the selection rectangle - */ - public Position getPosition() { - return mPosition; - } - - /** - * Returns the SWT cursor type to use for this selection handle - * - * @return the position of the handle in the selection rectangle - */ - public int getSwtCursorType() { - return mPosition.getCursorType(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionHandles.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionHandles.java deleted file mode 100644 index 6d7f34a66..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionHandles.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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.common.api.Margins; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.ResizePolicy; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.Position; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -/** - * The {@link SelectionHandles} of a {@link SelectionItem} are the set of - * {@link SelectionHandle} objects (possibly empty, for non-resizable objects) the user - * can manipulate to resize a widget. - */ -public class SelectionHandles implements Iterable<SelectionHandle> { - private final SelectionItem mItem; - private List<SelectionHandle> mHandles; - - /** - * Constructs a new {@link SelectionHandles} object for the given {link - * {@link SelectionItem} - * @param item the item to create {@link SelectionHandles} for - */ - public SelectionHandles(SelectionItem item) { - mItem = item; - - createHandles(item.getCanvas()); - } - - /** - * Find a specific {@link SelectionHandle} from this set of {@link SelectionHandles}, - * which is within the given distance (in layout coordinates) from the center of the - * {@link SelectionHandle}. - * - * @param point the mouse position (in layout coordinates) to test - * @param distance the maximum distance from the handle center to accept - * @return a {@link SelectionHandle} under the point, or null if not found - */ - public SelectionHandle findHandle(LayoutPoint point, int distance) { - for (SelectionHandle handle : mHandles) { - if (handle.contains(point, distance)) { - return handle; - } - } - - return null; - } - - /** - * Create the {@link SelectionHandle} objects for the selection item, according to its - * {@link ResizePolicy}. - */ - private void createHandles(LayoutCanvas canvas) { - NodeProxy selectedNode = mItem.getNode(); - Rect r = selectedNode.getBounds(); - if (!r.isValid()) { - mHandles = Collections.emptyList(); - return; - } - - ResizePolicy resizability = mItem.getResizePolicy(); - if (resizability.isResizable()) { - mHandles = new ArrayList<SelectionHandle>(8); - boolean left = resizability.leftAllowed(); - boolean right = resizability.rightAllowed(); - boolean top = resizability.topAllowed(); - boolean bottom = resizability.bottomAllowed(); - int x1 = r.x; - int y1 = r.y; - int w = r.w; - int h = r.h; - int x2 = x1 + w; - int y2 = y1 + h; - - Margins insets = canvas.getInsets(mItem.getNode().getFqcn()); - if (insets != null) { - x1 += insets.left; - x2 -= insets.right; - y1 += insets.top; - y2 -= insets.bottom; - } - - int mx = (x1 + x2) / 2; - int my = (y1 + y2) / 2; - - if (left) { - mHandles.add(new SelectionHandle(x1, my, Position.LEFT_MIDDLE)); - if (top) { - mHandles.add(new SelectionHandle(x1, y1, Position.TOP_LEFT)); - } - if (bottom) { - mHandles.add(new SelectionHandle(x1, y2, Position.BOTTOM_LEFT)); - } - } - if (right) { - mHandles.add(new SelectionHandle(x2, my, Position.RIGHT_MIDDLE)); - if (top) { - mHandles.add(new SelectionHandle(x2, y1, Position.TOP_RIGHT)); - } - if (bottom) { - mHandles.add(new SelectionHandle(x2, y2, Position.BOTTOM_RIGHT)); - } - } - if (top) { - mHandles.add(new SelectionHandle(mx, y1, Position.TOP_MIDDLE)); - } - if (bottom) { - mHandles.add(new SelectionHandle(mx, y2, Position.BOTTOM_MIDDLE)); - } - } else { - mHandles = Collections.emptyList(); - } - } - - // Implements Iterable<SelectionHandle> - @Override - public Iterator<SelectionHandle> iterator() { - return mHandles.iterator(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java deleted file mode 100644 index d104e379e..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.ResizePolicy; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -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 org.eclipse.swt.graphics.Rectangle; -import org.w3c.dom.Node; - -import java.util.ArrayList; -import java.util.List; - -/** - * Represents one selection in {@link LayoutCanvas}. - */ -class SelectionItem { - - /** The associated {@link LayoutCanvas} */ - private LayoutCanvas mCanvas; - - /** Current selected view info. Can be null. */ - private final CanvasViewInfo mCanvasViewInfo; - - /** Current selection border rectangle. Null when mCanvasViewInfo is null . */ - private final Rectangle mRect; - - /** The node proxy for drawing the selection. Null when mCanvasViewInfo is null. */ - private final NodeProxy mNodeProxy; - - /** The resize policy for this selection item */ - private ResizePolicy mResizePolicy; - - /** The selection handles for this item */ - private SelectionHandles mHandles; - - /** - * Creates a new {@link SelectionItem} object. - * @param canvas the associated canvas - * @param canvasViewInfo The view info being selected. Must not be null. - */ - public SelectionItem(LayoutCanvas canvas, CanvasViewInfo canvasViewInfo) { - assert canvasViewInfo != null; - - mCanvas = canvas; - mCanvasViewInfo = canvasViewInfo; - - if (canvasViewInfo == null) { - mRect = null; - mNodeProxy = null; - } else { - Rectangle r = canvasViewInfo.getSelectionRect(); - mRect = new Rectangle(r.x, r.y, r.width, r.height); - mNodeProxy = mCanvas.getNodeFactory().create(canvasViewInfo); - } - } - - /** - * Returns true when this selection item represents the root, the top level - * layout element in the editor. - * - * @return True if and only if this element is at the root of the hierarchy - */ - public boolean isRoot() { - return mCanvasViewInfo.isRoot(); - } - - /** - * Returns true if this item represents a widget that should not be manipulated by the - * user. - * - * @return True if this widget should not be manipulated directly by the user - */ - public boolean isHidden() { - return mCanvasViewInfo.isHidden(); - } - - /** - * Returns the selected view info. Cannot be null. - * - * @return the selected view info. Cannot be null. - */ - @NonNull - public CanvasViewInfo getViewInfo() { - return mCanvasViewInfo; - } - - /** - * Returns the selected node. - * - * @return the selected node, or null - */ - @Nullable - public UiViewElementNode getUiNode() { - return mCanvasViewInfo.getUiViewNode(); - } - - /** - * Returns the selection border rectangle. Cannot be null. - * - * @return the selection border rectangle, never null - */ - public Rectangle getRect() { - return mRect; - } - - /** Returns the node associated with this selection (may be null) */ - @Nullable - NodeProxy getNode() { - return mNodeProxy; - } - - /** Returns the canvas associated with this selection (never null) */ - @NonNull - LayoutCanvas getCanvas() { - return mCanvas; - } - - //---- - - /** - * Gets the XML text from the given selection for a text transfer. - * The returned string can be empty but not null. - */ - @NonNull - static String getAsText(LayoutCanvas canvas, List<SelectionItem> selection) { - StringBuilder sb = new StringBuilder(); - - LayoutEditorDelegate layoutEditorDelegate = canvas.getEditorDelegate(); - for (SelectionItem cs : selection) { - CanvasViewInfo vi = cs.getViewInfo(); - UiViewElementNode key = vi.getUiViewNode(); - Node node = key.getXmlNode(); - String t = layoutEditorDelegate.getEditor().getXmlText(node); - if (t != null) { - if (sb.length() > 0) { - sb.append('\n'); - } - sb.append(t); - } - } - - return sb.toString(); - } - - /** - * Returns elements representing the given selection of canvas items. - * - * @param items Items to wrap in elements - * @return An array of wrapper elements. Never null. - */ - @NonNull - static SimpleElement[] getAsElements(@NonNull List<SelectionItem> items) { - return getAsElements(items, null); - } - - /** - * Returns elements representing the given selection of canvas items. - * - * @param items Items to wrap in elements - * @param primary The primary selected item which should be listed first - * @return An array of wrapper elements. Never null. - */ - @NonNull - static SimpleElement[] getAsElements( - @NonNull List<SelectionItem> items, - @Nullable SelectionItem primary) { - List<SimpleElement> elements = new ArrayList<SimpleElement>(); - - if (primary != null) { - CanvasViewInfo vi = primary.getViewInfo(); - SimpleElement e = vi.toSimpleElement(); - e.setSelectionItem(primary); - elements.add(e); - } - - for (SelectionItem cs : items) { - if (cs == primary) { - // Already handled - continue; - } - - CanvasViewInfo vi = cs.getViewInfo(); - SimpleElement e = vi.toSimpleElement(); - e.setSelectionItem(cs); - elements.add(e); - } - - return elements.toArray(new SimpleElement[elements.size()]); - } - - /** - * Returns true if this selection item is a layout - * - * @return true if this selection item is a layout - */ - public boolean isLayout() { - UiViewElementNode node = mCanvasViewInfo.getUiViewNode(); - if (node != null) { - return node.getDescriptor().hasChildren(); - } else { - return false; - } - } - - /** - * Returns the {@link SelectionHandles} for this {@link SelectionItem}. Never null. - * - * @return the {@link SelectionHandles} for this {@link SelectionItem}, never null - */ - @NonNull - public SelectionHandles getSelectionHandles() { - if (mHandles == null) { - mHandles = new SelectionHandles(this); - } - - return mHandles; - } - - /** - * Returns the {@link ResizePolicy} for this item - * - * @return the {@link ResizePolicy} for this item, never null - */ - @NonNull - public ResizePolicy getResizePolicy() { - if (mResizePolicy == null && mNodeProxy != null) { - mResizePolicy = ViewMetadataRepository.get().getResizePolicy(mNodeProxy.getFqcn()); - } - - return mResizePolicy; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java deleted file mode 100644 index eb3d6f290..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java +++ /dev/null @@ -1,1262 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.FQCN_SPACE; -import static com.android.SdkConstants.FQCN_SPACE_V7; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_MARGIN; -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_RADIUS; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.RuleAction; -import com.android.ide.common.layout.BaseViewRule; -import com.android.ide.common.layout.GridLayoutRule; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -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.RulesEngine; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceWizard; -import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResult; -import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; -import com.android.resources.ResourceType; -import com.android.utils.Pair; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.ListenerList; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.Separator; -import org.eclipse.jface.dialogs.InputDialog; -import org.eclipse.jface.util.SafeRunnable; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.jface.viewers.ITreeSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.jface.viewers.TreeSelection; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MenuDetectEvent; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.ui.IWorkbenchPartSite; -import org.w3c.dom.Node; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; - -/** - * The {@link SelectionManager} manages the selection in the canvas editor. - * It holds (and can be asked about) the set of selected items, and it also has - * operations for manipulating the selection - such as toggling items, copying - * the selection to the clipboard, etc. - * <p/> - * This class implements {@link ISelectionProvider} so that it can delegate - * the selection provider from the {@link LayoutCanvasViewer}. - * <p/> - * Note that {@link LayoutCanvasViewer} sets a selection change listener on this - * manager so that it can invoke its own fireSelectionChanged when the canvas' - * selection changes. - */ -public class SelectionManager implements ISelectionProvider { - - private LayoutCanvas mCanvas; - - /** The current selection list. The list is never null, however it can be empty. */ - private final LinkedList<SelectionItem> mSelections = new LinkedList<SelectionItem>(); - - /** An unmodifiable view of {@link #mSelections}. */ - private final List<SelectionItem> mUnmodifiableSelection = - Collections.unmodifiableList(mSelections); - - /** Barrier set when updating the selection to prevent from recursively - * invoking ourselves. */ - private boolean mInsideUpdateSelection; - - /** - * The <em>current</em> alternate selection, if any, which changes when the Alt key is - * used during a selection. Can be null. - */ - private CanvasAlternateSelection mAltSelection; - - /** List of clients listening to selection changes. */ - private final ListenerList mSelectionListeners = new ListenerList(); - - /** - * Constructs a new {@link SelectionManager} associated with the given layout canvas. - * - * @param layoutCanvas The layout canvas to create a {@link SelectionManager} for. - */ - public SelectionManager(LayoutCanvas layoutCanvas) { - mCanvas = layoutCanvas; - } - - @Override - public void addSelectionChangedListener(ISelectionChangedListener listener) { - mSelectionListeners.add(listener); - } - - @Override - public void removeSelectionChangedListener(ISelectionChangedListener listener) { - mSelectionListeners.remove(listener); - } - - /** - * Returns the native {@link SelectionItem} list. - * - * @return An immutable list of {@link SelectionItem}. Can be empty but not null. - */ - @NonNull - List<SelectionItem> getSelections() { - return mUnmodifiableSelection; - } - - /** - * Return a snapshot/copy of the selection. Useful for clipboards etc where we - * don't want the returned copy to be affected by future edits to the selection. - * - * @return A copy of the current selection. Never null. - */ - @NonNull - public List<SelectionItem> getSnapshot() { - if (mSelectionListeners.isEmpty()) { - return Collections.emptyList(); - } - - return new ArrayList<SelectionItem>(mSelections); - } - - /** - * Returns a {@link TreeSelection} where each {@link TreePath} item is - * actually a {@link CanvasViewInfo}. - */ - @Override - public ISelection getSelection() { - if (mSelections.isEmpty()) { - return TreeSelection.EMPTY; - } - - ArrayList<TreePath> paths = new ArrayList<TreePath>(); - - for (SelectionItem cs : mSelections) { - CanvasViewInfo vi = cs.getViewInfo(); - if (vi != null) { - paths.add(getTreePath(vi)); - } - } - - return new TreeSelection(paths.toArray(new TreePath[paths.size()])); - } - - /** - * Create a {@link TreePath} from the given view info - * - * @param viewInfo the view info to look up a tree path for - * @return a {@link TreePath} for the given view info - */ - public static TreePath getTreePath(CanvasViewInfo viewInfo) { - ArrayList<Object> segments = new ArrayList<Object>(); - while (viewInfo != null) { - segments.add(0, viewInfo); - viewInfo = viewInfo.getParent(); - } - - return new TreePath(segments.toArray()); - } - - /** - * Sets the selection. It must be an {@link ITreeSelection} where each segment - * of the tree path is a {@link CanvasViewInfo}. A null selection is considered - * as an empty selection. - * <p/> - * This method is invoked by {@link LayoutCanvasViewer#setSelection(ISelection)} - * in response to an <em>outside</em> selection (compatible with ours) that has - * changed. Typically it means the outline selection has changed and we're - * synchronizing ours to match. - */ - @Override - public void setSelection(ISelection selection) { - if (mInsideUpdateSelection) { - return; - } - - boolean changed = false; - try { - mInsideUpdateSelection = true; - - if (selection == null) { - selection = TreeSelection.EMPTY; - } - - if (selection instanceof ITreeSelection) { - ITreeSelection treeSel = (ITreeSelection) selection; - - if (treeSel.isEmpty()) { - // Clear existing selection, if any - if (!mSelections.isEmpty()) { - mSelections.clear(); - mAltSelection = null; - updateActionsFromSelection(); - redraw(); - } - return; - } - - boolean redoLayout = false; - - // Create a list of all currently selected view infos - Set<CanvasViewInfo> oldSelected = new HashSet<CanvasViewInfo>(); - for (SelectionItem cs : mSelections) { - oldSelected.add(cs.getViewInfo()); - } - - // Go thru new selection and take care of selecting new items - // or marking those which are the same as in the current selection - for (TreePath path : treeSel.getPaths()) { - Object seg = path.getLastSegment(); - if (seg instanceof CanvasViewInfo) { - CanvasViewInfo newVi = (CanvasViewInfo) seg; - if (oldSelected.contains(newVi)) { - // This view info is already selected. Remove it from the - // oldSelected list so that we don't deselect it later. - oldSelected.remove(newVi); - } else { - // This view info is not already selected. Select it now. - - // reset alternate selection if any - mAltSelection = null; - // otherwise add it. - mSelections.add(createSelection(newVi)); - changed = true; - } - if (newVi.isInvisible()) { - redoLayout = true; - } - } else { - // Unrelated selection (e.g. user clicked in the Project Explorer - // or something) -- just ignore these - return; - } - } - - // Deselect old selected items that are not in the new one - for (CanvasViewInfo vi : oldSelected) { - if (vi.isExploded()) { - redoLayout = true; - } - deselect(vi); - changed = true; - } - - if (redoLayout) { - mCanvas.getEditorDelegate().recomputeLayout(); - } - } - } finally { - mInsideUpdateSelection = false; - } - - if (changed) { - redraw(); - fireSelectionChanged(); - updateActionsFromSelection(); - } - } - - /** - * The menu has been activated; ensure that the menu click is over the existing - * selection, and if not, update the selection. - * - * @param e the {@link MenuDetectEvent} which triggered the menu - */ - public void menuClick(MenuDetectEvent e) { - LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout(); - - // Right click button is used to display a context menu. - // If there's an existing selection and the click is anywhere in this selection - // and there are no modifiers being used, we don't want to change the selection. - // Otherwise we select the item under the cursor. - - for (SelectionItem cs : mSelections) { - if (cs.isRoot()) { - continue; - } - if (cs.getRect().contains(p.x, p.y)) { - // The cursor is inside the selection. Don't change anything. - return; - } - } - - CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); - selectSingle(vi); - } - - /** - * Performs selection for a mouse event. - * <p/> - * Shift key (or Command on the Mac) is used to toggle in multi-selection. - * Alt key is used to cycle selection through objects at the same level than - * the one pointed at (i.e. click on an object then alt-click to cycle). - * - * @param e The mouse event which triggered the selection. Cannot be null. - * The modifier key mask will be used to determine whether this - * is a plain select or a toggle, etc. - */ - public void select(MouseEvent e) { - boolean isMultiClick = (e.stateMask & SWT.SHIFT) != 0 || - // On Mac, the Command key is the normal toggle accelerator - ((SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) && - (e.stateMask & SWT.COMMAND) != 0); - boolean isCycleClick = (e.stateMask & SWT.ALT) != 0; - - LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout(); - - if (e.button == 3) { - // Right click button is used to display a context menu. - // If there's an existing selection and the click is anywhere in this selection - // and there are no modifiers being used, we don't want to change the selection. - // Otherwise we select the item under the cursor. - - if (!isCycleClick && !isMultiClick) { - for (SelectionItem cs : mSelections) { - if (cs.getRect().contains(p.x, p.y)) { - // The cursor is inside the selection. Don't change anything. - return; - } - } - } - - } else if (e.button != 1) { - // Click was done with something else than the left button for normal selection - // or the right button for context menu. - // We don't use mouse button 2 yet (middle mouse, or scroll wheel?) for - // anything, so let's not change the selection. - return; - } - - CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); - - if (vi != null && vi.isHidden()) { - vi = vi.getParent(); - } - - if (isMultiClick && !isCycleClick) { - // Case where shift is pressed: pointed object is toggled. - - // reset alternate selection if any - mAltSelection = null; - - // If nothing has been found at the cursor, assume it might be a user error - // and avoid clearing the existing selection. - - if (vi != null) { - // toggle this selection on-off: remove it if already selected - if (deselect(vi)) { - if (vi.isExploded()) { - mCanvas.getEditorDelegate().recomputeLayout(); - } - - redraw(); - return; - } - - // otherwise add it. - mSelections.add(createSelection(vi)); - fireSelectionChanged(); - redraw(); - } - - } else if (isCycleClick) { - // Case where alt is pressed: select or cycle the object pointed at. - - // Note: if shift and alt are pressed, shift is ignored. The alternate selection - // mechanism does not reset the current multiple selection unless they intersect. - - // We need to remember the "origin" of the alternate selection, to be - // able to continue cycling through it later. If there's no alternate selection, - // create one. If there's one but not for the same origin object, create a new - // one too. - if (mAltSelection == null || mAltSelection.getOriginatingView() != vi) { - mAltSelection = new CanvasAlternateSelection( - vi, mCanvas.getViewHierarchy().findAltViewInfoAt(p)); - - // deselect them all, in case they were partially selected - deselectAll(mAltSelection.getAltViews()); - - // select the current one - CanvasViewInfo vi2 = mAltSelection.getCurrent(); - if (vi2 != null) { - mSelections.addFirst(createSelection(vi2)); - fireSelectionChanged(); - } - } else { - // We're trying to cycle through the current alternate selection. - // First remove the current object. - CanvasViewInfo vi2 = mAltSelection.getCurrent(); - deselect(vi2); - - // Now select the next one. - vi2 = mAltSelection.getNext(); - if (vi2 != null) { - mSelections.addFirst(createSelection(vi2)); - fireSelectionChanged(); - } - } - redraw(); - - } else { - // Case where no modifier is pressed: either select or reset the selection. - selectSingle(vi); - } - } - - /** - * Removes all the currently selected item and only select the given item. - * Issues a redraw() if the selection changes. - * - * @param vi The new selected item if non-null. Selection becomes empty if null. - * @return the item selected, or null if the selection was cleared (e.g. vi was null) - */ - @Nullable - SelectionItem selectSingle(CanvasViewInfo vi) { - SelectionItem item = null; - - // reset alternate selection if any - mAltSelection = null; - - if (vi == null) { - // The user clicked outside the bounds of the root element; in that case, just - // select the root element. - vi = mCanvas.getViewHierarchy().getRoot(); - } - - boolean redoLayout = hasExplodedItems(); - - // reset (multi)selection if any - if (!mSelections.isEmpty()) { - if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) { - // CanvasSelection remains the same, don't touch it. - return mSelections.getFirst(); - } - mSelections.clear(); - } - - if (vi != null) { - item = createSelection(vi); - mSelections.add(item); - if (vi.isInvisible()) { - redoLayout = true; - } - } - fireSelectionChanged(); - - if (redoLayout) { - mCanvas.getEditorDelegate().recomputeLayout(); - } - - redraw(); - - return item; - } - - /** Returns true if the view hierarchy is showing exploded items. */ - private boolean hasExplodedItems() { - for (SelectionItem item : mSelections) { - if (item.getViewInfo().isExploded()) { - return true; - } - } - - return false; - } - - /** - * Selects the given set of {@link CanvasViewInfo}s. This is similar to - * {@link #selectSingle} but allows you to make a multi-selection. Issues a - * {@link #redraw()}. - * - * @param viewInfos A collection of {@link CanvasViewInfo} objects to be - * selected, or null or empty to clear the selection. - */ - /* package */ void selectMultiple(Collection<CanvasViewInfo> viewInfos) { - // reset alternate selection if any - mAltSelection = null; - - boolean redoLayout = hasExplodedItems(); - - mSelections.clear(); - if (viewInfos != null) { - for (CanvasViewInfo viewInfo : viewInfos) { - mSelections.add(createSelection(viewInfo)); - if (viewInfo.isInvisible()) { - redoLayout = true; - } - } - } - - fireSelectionChanged(); - - if (redoLayout) { - mCanvas.getEditorDelegate().recomputeLayout(); - } - - redraw(); - } - - public void select(Collection<INode> nodes) { - List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(nodes.size()); - for (INode node : nodes) { - CanvasViewInfo info = mCanvas.getViewHierarchy().findViewInfoFor(node); - if (info != null) { - infos.add(info); - } - } - selectMultiple(infos); - } - - /** - * Selects the visual element corresponding to the given XML node - * @param xmlNode The Node whose element we want to select. - */ - /* package */ void select(Node xmlNode) { - if (xmlNode == null) { - return; - } else if (xmlNode.getNodeType() == Node.TEXT_NODE) { - xmlNode = xmlNode.getParentNode(); - } - - CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoFor(xmlNode); - if (vi != null && !vi.isRoot()) { - selectSingle(vi); - } - } - - /** - * Selects any views that overlap the given selection rectangle. - * - * @param topLeft The top left corner defining the selection rectangle. - * @param bottomRight The bottom right corner defining the selection - * rectangle. - * @param toggled A set of {@link CanvasViewInfo}s that should be toggled - * rather than just added. - */ - public void selectWithin(LayoutPoint topLeft, LayoutPoint bottomRight, - Collection<CanvasViewInfo> toggled) { - // reset alternate selection if any - mAltSelection = null; - - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - Collection<CanvasViewInfo> viewInfos = viewHierarchy.findWithin(topLeft, bottomRight); - - if (toggled.size() > 0) { - // Copy; we're not allowed to touch the passed in collection - Set<CanvasViewInfo> result = new HashSet<CanvasViewInfo>(toggled); - for (CanvasViewInfo viewInfo : viewInfos) { - if (toggled.contains(viewInfo)) { - result.remove(viewInfo); - } else { - result.add(viewInfo); - } - } - viewInfos = result; - } - - mSelections.clear(); - for (CanvasViewInfo viewInfo : viewInfos) { - if (viewInfo.isHidden()) { - continue; - } - mSelections.add(createSelection(viewInfo)); - } - - fireSelectionChanged(); - redraw(); - } - - /** - * Clears the selection and then selects everything (all views and all their - * children). - */ - public void selectAll() { - // First clear the current selection, if any. - mSelections.clear(); - mAltSelection = null; - - // Now select everything if there's a valid layout - for (CanvasViewInfo vi : mCanvas.getViewHierarchy().findAllViewInfos(false)) { - mSelections.add(createSelection(vi)); - } - - fireSelectionChanged(); - redraw(); - } - - /** Clears the selection */ - public void selectNone() { - mSelections.clear(); - mAltSelection = null; - fireSelectionChanged(); - redraw(); - } - - /** Selects the parent of the current selection */ - public void selectParent() { - if (mSelections.size() == 1) { - CanvasViewInfo parent = mSelections.get(0).getViewInfo().getParent(); - if (parent != null) { - selectSingle(parent); - } - } - } - - /** Finds all widgets in the layout that have the same type as the primary */ - public void selectSameType() { - // Find all - if (mSelections.size() == 1) { - CanvasViewInfo viewInfo = mSelections.get(0).getViewInfo(); - ElementDescriptor descriptor = viewInfo.getUiViewNode().getDescriptor(); - mSelections.clear(); - mAltSelection = null; - addSameType(mCanvas.getViewHierarchy().getRoot(), descriptor); - fireSelectionChanged(); - redraw(); - } - } - - /** Helper for {@link #selectSameType} */ - private void addSameType(CanvasViewInfo root, ElementDescriptor descriptor) { - if (root.getUiViewNode().getDescriptor() == descriptor) { - mSelections.add(createSelection(root)); - } - - for (CanvasViewInfo child : root.getChildren()) { - addSameType(child, descriptor); - } - } - - /** Selects the siblings of the primary */ - public void selectSiblings() { - // Find all - if (mSelections.size() == 1) { - CanvasViewInfo vi = mSelections.get(0).getViewInfo(); - mSelections.clear(); - mAltSelection = null; - CanvasViewInfo parent = vi.getParent(); - if (parent == null) { - selectNone(); - } else { - for (CanvasViewInfo child : parent.getChildren()) { - mSelections.add(createSelection(child)); - } - fireSelectionChanged(); - redraw(); - } - } - } - - /** - * Returns true if and only if there is currently more than one selected - * item. - * - * @return True if more than one item is selected - */ - public boolean hasMultiSelection() { - return mSelections.size() > 1; - } - - /** - * Deselects a view info. Returns true if the object was actually selected. - * Callers are responsible for calling redraw() and updateOulineSelection() - * after. - * @param canvasViewInfo The item to deselect. - * @return True if the object was successfully removed from the selection. - */ - public boolean deselect(CanvasViewInfo canvasViewInfo) { - if (canvasViewInfo == null) { - return false; - } - - for (ListIterator<SelectionItem> it = mSelections.listIterator(); it.hasNext(); ) { - SelectionItem s = it.next(); - if (canvasViewInfo == s.getViewInfo()) { - it.remove(); - return true; - } - } - - return false; - } - - /** - * Deselects multiple view infos. - * Callers are responsible for calling redraw() and updateOulineSelection() after. - */ - private void deselectAll(List<CanvasViewInfo> canvasViewInfos) { - for (ListIterator<SelectionItem> it = mSelections.listIterator(); it.hasNext(); ) { - SelectionItem s = it.next(); - if (canvasViewInfos.contains(s.getViewInfo())) { - it.remove(); - } - } - } - - /** Sync the selection with an updated view info tree */ - void sync() { - // Check if the selection is still the same (based on the object keys) - // and eventually recompute their bounds. - for (ListIterator<SelectionItem> it = mSelections.listIterator(); it.hasNext(); ) { - SelectionItem s = it.next(); - - // Check if the selected object still exists - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - UiViewElementNode key = s.getViewInfo().getUiViewNode(); - CanvasViewInfo vi = viewHierarchy.findViewInfoFor(key); - - // Remove the previous selection -- if the selected object still exists - // we need to recompute its bounds in case it moved so we'll insert a new one - // at the same place. - it.remove(); - if (vi == null) { - vi = findCorresponding(s.getViewInfo(), viewHierarchy.getRoot()); - } - if (vi != null) { - it.add(createSelection(vi)); - } - } - fireSelectionChanged(); - - // remove the current alternate selection views - mAltSelection = null; - } - - /** Finds the corresponding {@link CanvasViewInfo} in the new hierarchy */ - private CanvasViewInfo findCorresponding(CanvasViewInfo old, CanvasViewInfo newRoot) { - CanvasViewInfo oldParent = old.getParent(); - if (oldParent != null) { - CanvasViewInfo newParent = findCorresponding(oldParent, newRoot); - if (newParent == null) { - return null; - } - - List<CanvasViewInfo> oldSiblings = oldParent.getChildren(); - List<CanvasViewInfo> newSiblings = newParent.getChildren(); - Iterator<CanvasViewInfo> oldIterator = oldSiblings.iterator(); - Iterator<CanvasViewInfo> newIterator = newSiblings.iterator(); - while (oldIterator.hasNext() && newIterator.hasNext()) { - CanvasViewInfo oldSibling = oldIterator.next(); - CanvasViewInfo newSibling = newIterator.next(); - - if (oldSibling.getName().equals(newSibling.getName())) { - // Structure has changed: can't do a proper search - return null; - } - - if (oldSibling == old) { - return newSibling; - } - } - } else { - return newRoot; - } - - return null; - } - - /** - * Notifies listeners that the selection has changed. - */ - private void fireSelectionChanged() { - if (mInsideUpdateSelection) { - return; - } - try { - mInsideUpdateSelection = true; - - final SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection()); - - SafeRunnable.run(new SafeRunnable() { - @Override - public void run() { - for (Object listener : mSelectionListeners.getListeners()) { - ((ISelectionChangedListener) listener).selectionChanged(event); - } - } - }); - - updateActionsFromSelection(); - } finally { - mInsideUpdateSelection = false; - } - } - - /** - * Updates menu actions and the layout action bar after a selection change - these are - * actions that depend on the selection - */ - private void updateActionsFromSelection() { - LayoutEditorDelegate editor = mCanvas.getEditorDelegate(); - if (editor != null) { - // Update menu actions that depend on the selection - mCanvas.updateMenuActionState(); - - // Update the layout actions bar - LayoutActionBar layoutActionBar = editor.getGraphicalEditor().getLayoutActionBar(); - layoutActionBar.updateSelection(); - } - } - - /** - * Sanitizes the selection for a copy/cut or drag operation. - * <p/> - * Sanitizes the list to make sure all elements have a valid XML attached to it, - * that is remove element that have no XML to avoid having to make repeated such - * checks in various places after. - * <p/> - * In case of multiple selection, we also need to remove all children when their - * parent is already selected since parents will always be added with all their - * children. - * <p/> - * - * @param selection The selection list to be sanitized <b>in-place</b>. - * The <code>selection</code> argument should not be {@link #mSelections} -- the - * given list is going to be altered and we should never alter the user-made selection. - * Instead the caller should provide its own copy. - */ - /* package */ static void sanitize(List<SelectionItem> selection) { - if (selection.isEmpty()) { - return; - } - - for (Iterator<SelectionItem> it = selection.iterator(); it.hasNext(); ) { - SelectionItem cs = it.next(); - CanvasViewInfo vi = cs.getViewInfo(); - UiViewElementNode key = vi == null ? null : vi.getUiViewNode(); - Node node = key == null ? null : key.getXmlNode(); - if (node == null) { - // Missing ViewInfo or view key or XML, discard this. - it.remove(); - continue; - } - - if (vi != null) { - for (Iterator<SelectionItem> it2 = selection.iterator(); - it2.hasNext(); ) { - SelectionItem cs2 = it2.next(); - if (cs != cs2) { - CanvasViewInfo vi2 = cs2.getViewInfo(); - if (vi.isParent(vi2)) { - // vi2 is a parent for vi. Remove vi. - it.remove(); - break; - } - } - } - } - } - } - - /** - * Selects the given list of nodes in the canvas, and returns true iff the - * attempt to select was successful. - * - * @param nodes The collection of nodes to be selected - * @param indices A list of indices within the parent for each node, or null - * @return True if and only if all nodes were successfully selected - */ - public boolean selectDropped(List<INode> nodes, List<Integer> indices) { - assert indices == null || nodes.size() == indices.size(); - - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - - // Look up a list of view infos which correspond to the nodes. - final Collection<CanvasViewInfo> newChildren = new ArrayList<CanvasViewInfo>(); - for (int i = 0, n = nodes.size(); i < n; i++) { - INode node = nodes.get(i); - - CanvasViewInfo viewInfo = viewHierarchy.findViewInfoFor(node); - - // There are two scenarios where looking up a view info fails. - // The first one is that the node was just added and the render has not yet - // happened, so the ViewHierarchy has no record of the node. In this case - // there is nothing we can do, and the method will return false (which the - // caller will use to schedule a second attempt later). - // The second scenario is where the nodes *change identity*. This isn't - // common, but when a drop handler makes a lot of changes to its children, - // for example when dropping into a GridLayout where attributes are adjusted - // on nearly all the other children to update row or column attributes - // etc, then in some cases Eclipse's DOM model changes the identities of - // the nodes when applying all the edits, so the new Node we created (as - // well as possibly other nodes) are no longer the children we observe - // after the edit, and there are new copies there instead. In this case - // the UiViewModel also fails to map the nodes. To work around this, - // we track the *indices* (within the parent) during a drop, such that we - // know which children (according to their positions) the given nodes - // are supposed to map to, and then we use these view infos instead. - if (viewInfo == null && node instanceof NodeProxy && indices != null) { - INode parent = node.getParent(); - CanvasViewInfo parentViewInfo = viewHierarchy.findViewInfoFor(parent); - if (parentViewInfo != null) { - UiViewElementNode parentUiNode = parentViewInfo.getUiViewNode(); - if (parentUiNode != null) { - List<UiElementNode> children = parentUiNode.getUiChildren(); - int index = indices.get(i); - if (index >= 0 && index < children.size()) { - UiElementNode replacedNode = children.get(index); - viewInfo = viewHierarchy.findViewInfoFor(replacedNode); - } - } - } - } - - if (viewInfo != null) { - if (nodes.size() > 1 && viewInfo.isHidden()) { - // Skip spacers - unless you're dropping just one - continue; - } - if (GridLayoutRule.sDebugGridLayout && (viewInfo.getName().equals(FQCN_SPACE) - || viewInfo.getName().equals(FQCN_SPACE_V7))) { - // In debug mode they might not be marked as hidden but we never never - // want to select these guys - continue; - } - newChildren.add(viewInfo); - } - } - boolean found = nodes.size() == newChildren.size(); - - if (found || newChildren.size() > 0) { - mCanvas.getSelectionManager().selectMultiple(newChildren); - } - - return found; - } - - /** - * Update the outline selection to select the given nodes, asynchronously. - * @param nodes The nodes to be selected - */ - public void setOutlineSelection(final List<INode> nodes) { - Display.getDefault().asyncExec(new Runnable() { - @Override - public void run() { - selectDropped(nodes, null /* indices */); - syncOutlineSelection(); - } - }); - } - - /** - * Syncs the current selection to the outline, synchronously. - */ - public void syncOutlineSelection() { - OutlinePage outlinePage = mCanvas.getOutlinePage(); - IWorkbenchPartSite site = outlinePage.getEditor().getSite(); - ISelectionProvider selectionProvider = site.getSelectionProvider(); - ISelection selection = selectionProvider.getSelection(); - if (selection != null) { - outlinePage.setSelection(selection); - } - } - - private void redraw() { - mCanvas.redraw(); - } - - SelectionItem createSelection(CanvasViewInfo vi) { - return new SelectionItem(mCanvas, vi); - } - - /** - * Returns true if there is nothing selected - * - * @return true if there is nothing selected - */ - public boolean isEmpty() { - return mSelections.size() == 0; - } - - /** - * "Select" context menu which lists various menu options related to selection: - * <ul> - * <li> Select All - * <li> Select Parent - * <li> Select None - * <li> Select Siblings - * <li> Select Same Type - * </ul> - * etc. - */ - public static class SelectionMenu extends SubmenuAction { - private final GraphicalEditorPart mEditor; - - public SelectionMenu(GraphicalEditorPart editor) { - super("Select"); - mEditor = editor; - } - - @Override - public String getId() { - return "-selectionmenu"; //$NON-NLS-1$ - } - - @Override - protected void addMenuItems(Menu menu) { - LayoutCanvas canvas = mEditor.getCanvasControl(); - SelectionManager selectionManager = canvas.getSelectionManager(); - List<SelectionItem> selections = selectionManager.getSelections(); - boolean selectedOne = selections.size() == 1; - boolean notRoot = selectedOne && !selections.get(0).isRoot(); - boolean haveSelection = selections.size() > 0; - - Action a; - a = selectionManager.new SelectAction("Select Parent\tEsc", SELECT_PARENT); - new ActionContributionItem(a).fill(menu, -1); - a.setEnabled(notRoot); - a.setAccelerator(SWT.ESC); - - a = selectionManager.new SelectAction("Select Siblings", SELECT_SIBLINGS); - new ActionContributionItem(a).fill(menu, -1); - a.setEnabled(notRoot); - - a = selectionManager.new SelectAction("Select Same Type", SELECT_SAME_TYPE); - new ActionContributionItem(a).fill(menu, -1); - a.setEnabled(selectedOne); - - new Separator().fill(menu, -1); - - // Special case for Select All: Use global action - a = canvas.getSelectAllAction(); - new ActionContributionItem(a).fill(menu, -1); - a.setEnabled(true); - - a = selectionManager.new SelectAction("Deselect All", SELECT_NONE); - new ActionContributionItem(a).fill(menu, -1); - a.setEnabled(haveSelection); - } - } - - private static final int SELECT_PARENT = 1; - private static final int SELECT_SIBLINGS = 2; - private static final int SELECT_SAME_TYPE = 3; - private static final int SELECT_NONE = 4; // SELECT_ALL is handled separately - - private class SelectAction extends Action { - private final int mType; - - public SelectAction(String title, int type) { - super(title, IAction.AS_PUSH_BUTTON); - mType = type; - } - - @Override - public void run() { - switch (mType) { - case SELECT_NONE: - selectNone(); - break; - case SELECT_PARENT: - selectParent(); - break; - case SELECT_SAME_TYPE: - selectSameType(); - break; - case SELECT_SIBLINGS: - selectSiblings(); - break; - } - - List<INode> nodes = new ArrayList<INode>(); - for (SelectionItem item : getSelections()) { - nodes.add(item.getNode()); - } - setOutlineSelection(nodes); - } - } - - public Pair<SelectionItem, SelectionHandle> findHandle(ControlPoint controlPoint) { - if (!isEmpty()) { - LayoutPoint layoutPoint = controlPoint.toLayout(); - int distance = (int) ((PIXEL_MARGIN + PIXEL_RADIUS) / mCanvas.getScale()); - - for (SelectionItem item : getSelections()) { - SelectionHandles handles = item.getSelectionHandles(); - // See if it's over the selection handles - SelectionHandle handle = handles.findHandle(layoutPoint, distance); - if (handle != null) { - return Pair.of(item, handle); - } - } - - } - return null; - } - - /** Performs the default action provided by the currently selected view */ - public void performDefaultAction() { - final List<SelectionItem> selections = getSelections(); - if (selections.size() > 0) { - NodeProxy primary = selections.get(0).getNode(); - if (primary != null) { - RulesEngine rulesEngine = mCanvas.getRulesEngine(); - final String id = rulesEngine.callGetDefaultActionId(primary); - if (id == null) { - return; - } - final List<RuleAction> actions = rulesEngine.callGetContextMenu(primary); - if (actions == null) { - return; - } - RuleAction matching = null; - for (RuleAction a : actions) { - if (id.equals(a.getId())) { - matching = a; - break; - } - } - if (matching == null) { - return; - } - final List<INode> selectedNodes = new ArrayList<INode>(); - for (SelectionItem item : selections) { - NodeProxy n = item.getNode(); - if (n != null) { - selectedNodes.add(n); - } - } - final RuleAction action = matching; - mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel(action.getTitle(), - new Runnable() { - @Override - public void run() { - action.getCallback().action(action, selectedNodes, - action.getId(), null); - LayoutCanvas canvas = mCanvas; - CanvasViewInfo root = canvas.getViewHierarchy().getRoot(); - if (root != null) { - UiViewElementNode uiViewNode = root.getUiViewNode(); - NodeFactory nodeFactory = canvas.getNodeFactory(); - NodeProxy rootNode = nodeFactory.create(uiViewNode); - if (rootNode != null) { - rootNode.applyPendingChanges(); - } - } - } - }); - } - } - } - - /** Performs renaming the selected views */ - public void performRename() { - final List<SelectionItem> selections = getSelections(); - if (selections.size() > 0) { - NodeProxy primary = selections.get(0).getNode(); - if (primary != null) { - performRename(primary, selections); - } - } - } - - /** - * Performs renaming the given node. - * - * @param primary the node to be renamed, or the primary node (to get the - * current value from if more than one node should be renamed) - * @param selections if not null, a list of nodes to apply the setting to - * (which should include the primary) - * @return the result of the renaming operation - */ - @NonNull - public RenameResult performRename( - final @NonNull INode primary, - final @Nullable List<SelectionItem> selections) { - String id = primary.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null && !id.isEmpty()) { - RenameResult result = RenameResourceWizard.renameResource( - mCanvas.getShell(), - mCanvas.getEditorDelegate().getGraphicalEditor().getProject(), - ResourceType.ID, BaseViewRule.stripIdPrefix(id), null, true /*canClear*/); - if (result.isCanceled()) { - return result; - } else if (!result.isUnavailable()) { - return result; - } - } - String currentId = primary.getStringAttr(ANDROID_URI, ATTR_ID); - currentId = BaseViewRule.stripIdPrefix(currentId); - InputDialog d = new InputDialog( - AdtPlugin.getDisplay().getActiveShell(), - "Set ID", - "New ID:", - currentId, - ResourceNameValidator.create(false, (IProject) null, ResourceType.ID)); - if (d.open() == Window.OK) { - final String s = d.getValue(); - mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel("Set ID", - new Runnable() { - @Override - public void run() { - String newId = s; - newId = NEW_ID_PREFIX + BaseViewRule.stripIdPrefix(s); - if (selections != null) { - for (SelectionItem item : selections) { - NodeProxy node = item.getNode(); - if (node != null) { - node.setAttribute(ANDROID_URI, ATTR_ID, newId); - } - } - } else { - primary.setAttribute(ANDROID_URI, ATTR_ID, newId); - } - - LayoutCanvas canvas = mCanvas; - CanvasViewInfo root = canvas.getViewHierarchy().getRoot(); - if (root != null) { - UiViewElementNode uiViewNode = root.getUiViewNode(); - NodeFactory nodeFactory = canvas.getNodeFactory(); - NodeProxy rootNode = nodeFactory.create(uiViewNode); - if (rootNode != null) { - rootNode.applyPendingChanges(); - } - } - } - }); - return RenameResult.name(BaseViewRule.stripIdPrefix(s)); - } else { - return RenameResult.canceled(); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java deleted file mode 100644 index 97d048108..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2010 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.common.api.DrawingStyle; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Rect; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; - -import org.eclipse.swt.graphics.GC; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * The {@link SelectionOverlay} paints the current selection as an overlay. - */ -public class SelectionOverlay extends Overlay { - private final LayoutCanvas mCanvas; - private boolean mHidden; - - /** - * Constructs a new {@link SelectionOverlay} tied to the given canvas. - * - * @param canvas the associated canvas - */ - public SelectionOverlay(LayoutCanvas canvas) { - mCanvas = canvas; - } - - /** - * Set whether the selection overlay should be hidden. This is done during some - * gestures like resize where the new bounds could be confused with the current - * selection bounds. - * - * @param hidden when true, hide the selection bounds, when false, unhide. - */ - public void setHidden(boolean hidden) { - mHidden = hidden; - } - - /** - * Paints the selection. - * - * @param selectionManager The {@link SelectionManager} holding the - * selection. - * @param gcWrapper The graphics context wrapper for the layout rules to use. - * @param gc The SWT graphics object - * @param rulesEngine The {@link RulesEngine} holding the rules. - */ - public void paint(SelectionManager selectionManager, GCWrapper gcWrapper, - GC gc, RulesEngine rulesEngine) { - if (mHidden) { - return; - } - - List<SelectionItem> selections = selectionManager.getSelections(); - int n = selections.size(); - if (n > 0) { - List<NodeProxy> selectedNodes = new ArrayList<NodeProxy>(); - boolean isMultipleSelection = n > 1; - for (SelectionItem s : selections) { - if (s.isRoot()) { - // The root selection is never painted - continue; - } - - NodeProxy node = s.getNode(); - if (node != null) { - paintSelection(gcWrapper, gc, s, isMultipleSelection); - selectedNodes.add(node); - } - } - - if (selectedNodes.size() > 0) { - paintSelectionFeedback(gcWrapper, selectedNodes, rulesEngine); - } else { - CanvasViewInfo root = mCanvas.getViewHierarchy().getRoot(); - if (root != null) { - NodeProxy parent = mCanvas.getNodeFactory().create(root); - rulesEngine.callPaintSelectionFeedback(gcWrapper, - parent, Collections.<INode>emptyList(), root.getViewObject()); - } - } - - if (n == 1) { - NodeProxy node = selections.get(0).getNode(); - if (node != null) { - paintHints(gcWrapper, node, rulesEngine); - } - } - } else { - CanvasViewInfo root = mCanvas.getViewHierarchy().getRoot(); - if (root != null) { - NodeProxy parent = mCanvas.getNodeFactory().create(root); - rulesEngine.callPaintSelectionFeedback(gcWrapper, - parent, Collections.<INode>emptyList(), root.getViewObject()); - } - } - } - - /** Paint hint for current selection */ - private void paintHints(GCWrapper gcWrapper, NodeProxy node, RulesEngine rulesEngine) { - INode parent = node.getParent(); - if (parent instanceof NodeProxy) { - NodeProxy parentNode = (NodeProxy) parent; - List<String> infos = rulesEngine.callGetSelectionHint(parentNode, node); - if (infos != null && infos.size() > 0) { - gcWrapper.useStyle(DrawingStyle.HELP); - - Rect b = mCanvas.getImageOverlay().getImageBounds(); - if (b == null) { - return; - } - - // Compute the location to display the help. This is done in - // layout coordinates, so we need to apply the scale in reverse - // when making pixel margins - // TODO: We could take the Canvas dimensions into account to see - // where there is more room. - // TODO: The scrollbars should take the presence of hint text - // into account. - double scale = mCanvas.getScale(); - int x, y; - if (b.w > b.h) { - x = (int) (b.x + 3 / scale); - y = (int) (b.y + b.h + 6 / scale); - } else { - x = (int) (b.x + b.w + 6 / scale); - y = (int) (b.y + 3 / scale); - } - gcWrapper.drawBoxedStrings(x, y, infos); - } - } - } - - private void paintSelectionFeedback(GCWrapper gcWrapper, List<NodeProxy> nodes, - RulesEngine rulesEngine) { - // Add fastpath for n=1 - - // Group nodes into parent/child groups - Set<INode> parents = new HashSet<INode>(); - for (INode node : nodes) { - INode parent = node.getParent(); - if (/*parent == null || */parent instanceof NodeProxy) { - NodeProxy parentNode = (NodeProxy) parent; - parents.add(parentNode); - } - } - ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); - for (INode parent : parents) { - List<INode> children = new ArrayList<INode>(); - for (INode node : nodes) { - INode nodeParent = node.getParent(); - if (nodeParent == parent) { - children.add(node); - } - } - CanvasViewInfo viewInfo = viewHierarchy.findViewInfoFor((NodeProxy) parent); - Object view = viewInfo != null ? viewInfo.getViewObject() : null; - - rulesEngine.callPaintSelectionFeedback(gcWrapper, - (NodeProxy) parent, children, view); - } - } - - /** Called by the canvas when a view is being selected. */ - private void paintSelection(IGraphics gc, GC swtGc, SelectionItem item, - boolean isMultipleSelection) { - CanvasViewInfo view = item.getViewInfo(); - if (view.isHidden()) { - return; - } - - NodeProxy selectedNode = item.getNode(); - Rect r = selectedNode.getBounds(); - if (!r.isValid()) { - return; - } - - gc.useStyle(DrawingStyle.SELECTION); - - Margins insets = mCanvas.getInsets(selectedNode.getFqcn()); - int x1 = r.x; - int y1 = r.y; - int x2 = r.x2() + 1; - int y2 = r.y2() + 1; - - if (insets != null) { - x1 += insets.left; - x2 -= insets.right; - y1 += insets.top; - y2 -= insets.bottom; - } - - gc.drawRect(x1, y1, x2, y2); - - // Paint sibling rectangles, if applicable - List<CanvasViewInfo> siblings = view.getNodeSiblings(); - if (siblings != null) { - for (CanvasViewInfo sibling : siblings) { - if (sibling != view) { - r = SwtUtils.toRect(sibling.getSelectionRect()); - gc.fillRect(r); - gc.drawRect(r); - } - } - } - - // Paint selection handles. These are painted in control coordinates on the - // real SWT GC object rather than in layout coordinates on the GCWrapper, - // since we want them to have a fixed size that is independent of the - // screen zoom. - CanvasTransform horizontalTransform = mCanvas.getHorizontalTransform(); - CanvasTransform verticalTransform = mCanvas.getVerticalTransform(); - int radius = SelectionHandle.PIXEL_RADIUS; - int doubleRadius = 2 * radius; - for (SelectionHandle handle : item.getSelectionHandles()) { - int cx = horizontalTransform.translate(handle.centerX); - int cy = verticalTransform.translate(handle.centerY); - - SwtDrawingStyle style = SwtDrawingStyle.of(DrawingStyle.SELECTION); - gc.setAlpha(style.getStrokeAlpha()); - swtGc.fillRectangle(cx - radius, cy - radius, doubleRadius, doubleRadius); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ShowWithinMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ShowWithinMenu.java deleted file mode 100644 index d1d529e5a..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ShowWithinMenu.java +++ /dev/null @@ -1,82 +0,0 @@ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.ide.common.rendering.api.Capability; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.Separator; -import org.eclipse.swt.widgets.Menu; - -import java.util.List; - -/** - * Action which creates a submenu for the "Show Included In" action - */ -class ShowWithinMenu extends SubmenuAction { - private LayoutEditorDelegate mEditorDelegate; - - ShowWithinMenu(LayoutEditorDelegate editorDelegate) { - super("Show Included In"); - mEditorDelegate = editorDelegate; - } - - @Override - protected void addMenuItems(Menu menu) { - GraphicalEditorPart graphicalEditor = mEditorDelegate.getGraphicalEditor(); - IFile file = graphicalEditor.getEditedFile(); - if (graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) { - IProject project = file.getProject(); - IncludeFinder finder = IncludeFinder.get(project); - final List<Reference> includedBy = finder.getIncludedBy(file); - - if (includedBy != null && includedBy.size() > 0) { - for (final Reference reference : includedBy) { - String title = reference.getDisplayName(); - IAction action = new ShowWithinAction(title, reference); - new ActionContributionItem(action).fill(menu, -1); - } - new Separator().fill(menu, -1); - } - IAction action = new ShowWithinAction("Nothing", null); - if (includedBy == null || includedBy.size() == 0) { - action.setEnabled(false); - } - new ActionContributionItem(action).fill(menu, -1); - } else { - addDisabledMessageItem("Not supported on platform"); - } - } - - /** Action to select one particular include-context */ - private class ShowWithinAction extends Action { - private Reference mReference; - - public ShowWithinAction(String title, Reference reference) { - super(title, IAction.AS_RADIO_BUTTON); - mReference = reference; - } - - @Override - public boolean isChecked() { - Reference within = mEditorDelegate.getGraphicalEditor().getIncludedWithin(); - if (within == null) { - return mReference == null; - } else { - return within.equals(mReference); - } - } - - @Override - public void run() { - if (!isChecked()) { - mEditorDelegate.getGraphicalEditor().showIn(mReference); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleAttribute.java deleted file mode 100644 index 198c16484..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleAttribute.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode.IAttribute; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Represents one XML attribute in a {@link SimpleElement}. - * <p/> - * The attribute is always represented by a namespace URI, a name and a value. - * The name cannot be empty. - * The namespace URI can be empty for an attribute without a namespace but is never null. - * The value can be empty but cannot be null. - * <p/> - * For a more detailed explanation of the purpose of this class, - * please see {@link SimpleXmlTransfer}. - */ -public class SimpleAttribute implements IAttribute { - private final String mName; - private final String mValue; - private final String mUri; - - /** - * Creates a new {@link SimpleAttribute}. - * <p/> - * Any null value will be converted to an empty non-null string. - * However it is a semantic error to use an empty name -- no assertion is done though. - * - * @param uri The URI of the attribute. - * @param name The XML local name of the attribute. - * @param value The value of the attribute. - */ - public SimpleAttribute(String uri, String name, String value) { - mUri = uri == null ? "" : uri; - mName = name == null ? "" : name; - mValue = value == null ? "" : value; - } - - /** - * Returns the namespace URI of the attribute. - * Can be empty for an attribute without a namespace but is never null. - */ - @Override - public @NonNull String getUri() { - return mUri; - } - - /** Returns the XML local name of the attribute. Cannot be null nor empty. */ - @Override - public @NonNull String getName() { - return mName; - } - - /** Returns the value of the attribute. Cannot be null. Can be empty. */ - @Override - public @NonNull String getValue() { - return mValue; - } - - // reader and writer methods - - @Override - public String toString() { - return String.format("@%s:%s=%s\n", //$NON-NLS-1$ - mName, - mUri, - mValue); - } - - private static final Pattern REGEXP = - Pattern.compile("[^@]*@([^:]+):([^=]*)=([^\n]*)\n*"); //$NON-NLS-1$ - - static SimpleAttribute parseString(String value) { - Matcher m = REGEXP.matcher(value); - if (m.matches()) { - return new SimpleAttribute(m.group(2), m.group(1), m.group(3)); - } - - return null; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SimpleAttribute) { - SimpleAttribute sa = (SimpleAttribute) obj; - - return mName.equals(sa.mName) && - mUri.equals(sa.mUri) && - mValue.equals(sa.mValue); - } - return false; - } - - @Override - public int hashCode() { - long c = mName.hashCode(); - // uses the formula defined in java.util.List.hashCode() - c = 31*c + mUri.hashCode(); - c = 31*c + mValue.hashCode(); - if (c > 0x0FFFFFFFFL) { - // wrap any overflow - c = c ^ (c >> 32); - } - return (int)(c & 0x0FFFFFFFFL); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java deleted file mode 100644 index 9acc8c25e..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Rect; - -import java.util.ArrayList; -import java.util.List; - -/** - * Represents an XML element with a name, attributes and inner elements. - * <p/> - * The semantic of the element name is to be a fully qualified class name of a View to inflate. - * The element name is not expected to have a name space. - * <p/> - * For a more detailed explanation of the purpose of this class, - * please see {@link SimpleXmlTransfer}. - */ -public class SimpleElement implements IDragElement { - - /** Version number of the internal serialized string format. */ - private static final String FORMAT_VERSION = "3"; - - private final String mFqcn; - private final String mParentFqcn; - private final Rect mBounds; - private final Rect mParentBounds; - private final List<IDragAttribute> mAttributes = new ArrayList<IDragAttribute>(); - private final List<IDragElement> mElements = new ArrayList<IDragElement>(); - - private IDragAttribute[] mCachedAttributes = null; - private IDragElement[] mCachedElements = null; - private SelectionItem mSelectionItem; - - /** - * Creates a new {@link SimpleElement} with the specified element name. - * - * @param fqcn A fully qualified class name of a View to inflate, e.g. - * "android.view.Button". Must not be null nor empty. - * @param parentFqcn The fully qualified class name of the parent of this element. - * Can be null but not empty. - * @param bounds The canvas bounds of the originating canvas node of the element. - * If null, a non-null invalid rectangle will be assigned. - * @param parentBounds The canvas bounds of the parent of this element. Can be null. - */ - public SimpleElement(String fqcn, String parentFqcn, Rect bounds, Rect parentBounds) { - mFqcn = fqcn; - mParentFqcn = parentFqcn; - mBounds = bounds == null ? new Rect() : bounds.copy(); - mParentBounds = parentBounds == null ? new Rect() : parentBounds.copy(); - } - - /** - * Returns the element name, which must match a fully qualified class name of - * a View to inflate. - */ - @Override - public @NonNull String getFqcn() { - return mFqcn; - } - - /** - * Returns the bounds of the element's node, if it originated from an existing - * canvas. The rectangle is invalid and non-null when the element originated - * from the object palette (unless it successfully rendered a preview) - */ - @Override - public @NonNull Rect getBounds() { - return mBounds; - } - - /** - * Returns the fully qualified class name of the parent, if the element originated - * from an existing canvas. Returns null if the element has no parent, such as a top - * level element or an element originating from the object palette. - */ - @Override - public String getParentFqcn() { - return mParentFqcn; - } - - /** - * Returns the bounds of the element's parent, absolute for the canvas, or null if there - * is no suitable parent. This is null when {@link #getParentFqcn()} is null. - */ - @Override - public @NonNull Rect getParentBounds() { - return mParentBounds; - } - - @Override - public @NonNull IDragAttribute[] getAttributes() { - if (mCachedAttributes == null) { - mCachedAttributes = mAttributes.toArray(new IDragAttribute[mAttributes.size()]); - } - return mCachedAttributes; - } - - @Override - public IDragAttribute getAttribute(@Nullable String uri, @NonNull String localName) { - for (IDragAttribute attr : mAttributes) { - if (attr.getUri().equals(uri) && attr.getName().equals(localName)) { - return attr; - } - } - - return null; - } - - @Override - public @NonNull IDragElement[] getInnerElements() { - if (mCachedElements == null) { - mCachedElements = mElements.toArray(new IDragElement[mElements.size()]); - } - return mCachedElements; - } - - public void addAttribute(SimpleAttribute attr) { - mCachedAttributes = null; - mAttributes.add(attr); - } - - public void addInnerElement(SimpleElement e) { - mCachedElements = null; - mElements.add(e); - } - - @Override - public boolean isSame(@NonNull INode node) { - if (mSelectionItem != null) { - return node == mSelectionItem.getNode(); - } else { - return node.getBounds().equals(mBounds); - } - } - - void setSelectionItem(@Nullable SelectionItem selectionItem) { - mSelectionItem = selectionItem; - } - - @Nullable - SelectionItem getSelectionItem() { - return mSelectionItem; - } - - @Nullable - static SimpleElement findPrimary(SimpleElement[] elements, SelectionItem primary) { - if (elements == null || elements.length == 0) { - return null; - } - - if (elements.length == 1 || primary == null) { - return elements[0]; - } - - for (SimpleElement element : elements) { - if (element.getSelectionItem() == primary) { - return element; - } - } - - return elements[0]; - } - - // reader and writer methods - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("{V=").append(FORMAT_VERSION); - sb.append(",N=").append(mFqcn); - if (mParentFqcn != null) { - sb.append(",P=").append(mParentFqcn); - } - if (mBounds != null && mBounds.isValid()) { - sb.append(String.format(",R=%d %d %d %d", mBounds.x, mBounds.y, mBounds.w, mBounds.h)); - } - if (mParentBounds != null && mParentBounds.isValid()) { - sb.append(String.format(",Q=%d %d %d %d", - mParentBounds.x, mParentBounds.y, mParentBounds.w, mParentBounds.h)); - } - sb.append('\n'); - for (IDragAttribute a : mAttributes) { - sb.append(a.toString()); - } - for (IDragElement e : mElements) { - sb.append(e.toString()); - } - sb.append("}\n"); //$NON-NLS-1$ - return sb.toString(); - } - - /** Parses a string containing one or more elements. */ - static SimpleElement[] parseString(String value) { - ArrayList<SimpleElement> elements = new ArrayList<SimpleElement>(); - String[] lines = value.split("\n"); - int[] index = new int[] { 0 }; - SimpleElement element = null; - while ((element = parseLines(lines, index)) != null) { - elements.add(element); - } - return elements.toArray(new SimpleElement[elements.size()]); - } - - /** - * Parses one element from the input lines array, starting at the inOutIndex - * and updating the inOutIndex to match the next unread line on output. - */ - private static SimpleElement parseLines(String[] lines, int[] inOutIndex) { - SimpleElement e = null; - int index = inOutIndex[0]; - while (index < lines.length) { - String line = lines[index++]; - String s = line.trim(); - if (s.startsWith("{")) { //$NON-NLS-1$ - if (e == null) { - // This is the element's header, it should have - // the format "key=value,key=value,..." - String version = null; - String fqcn = null; - String parent = null; - Rect bounds = null; - Rect pbounds = null; - - for (String s2 : s.substring(1).split(",")) { //$NON-NLS-1$ - int pos = s2.indexOf('='); - if (pos <= 0 || pos == s2.length() - 1) { - continue; - } - String key = s2.substring(0, pos).trim(); - String value = s2.substring(pos + 1).trim(); - - if (key.equals("V")) { //$NON-NLS-1$ - version = value; - if (!value.equals(FORMAT_VERSION)) { - // Wrong format version. Don't even try to process anything - // else and just give up everything. - inOutIndex[0] = index; - return null; - } - - } else if (key.equals("N")) { //$NON-NLS-1$ - fqcn = value; - - } else if (key.equals("P")) { //$NON-NLS-1$ - parent = value; - - } else if (key.equals("R") || key.equals("Q")) { //$NON-NLS-1$ //$NON-NLS-2$ - // Parse the canvas bounds - String[] sb = value.split(" +"); //$NON-NLS-1$ - if (sb != null && sb.length == 4) { - Rect r = null; - try { - r = new Rect(); - r.x = Integer.parseInt(sb[0]); - r.y = Integer.parseInt(sb[1]); - r.w = Integer.parseInt(sb[2]); - r.h = Integer.parseInt(sb[3]); - - if (key.equals("R")) { - bounds = r; - } else { - pbounds = r; - } - } catch (NumberFormatException ignore) { - } - } - } - } - - // We need at least a valid name to recreate an element - if (version != null && fqcn != null && fqcn.length() > 0) { - e = new SimpleElement(fqcn, parent, bounds, pbounds); - } - } else { - // This is an inner element... need to parse the { line again. - inOutIndex[0] = index - 1; - SimpleElement e2 = SimpleElement.parseLines(lines, inOutIndex); - if (e2 != null) { - e.addInnerElement(e2); - } - index = inOutIndex[0]; - } - - } else if (e != null && s.startsWith("@")) { //$NON-NLS-1$ - SimpleAttribute a = SimpleAttribute.parseString(line); - if (a != null) { - e.addAttribute(a); - } - - } else if (e != null && s.startsWith("}")) { //$NON-NLS-1$ - // We're done with this element - inOutIndex[0] = index; - return e; - } - } - inOutIndex[0] = index; - return null; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SimpleElement) { - SimpleElement se = (SimpleElement) obj; - - // Bounds and parentFqcn must be null on both sides or equal. - if ((mBounds == null && se.mBounds != null) || - (mBounds != null && !mBounds.equals(se.mBounds))) { - return false; - } - if ((mParentFqcn == null && se.mParentFqcn != null) || - (mParentFqcn != null && !mParentFqcn.equals(se.mParentFqcn))) { - return false; - } - if ((mParentBounds == null && se.mParentBounds != null) || - (mParentBounds != null && !mParentBounds.equals(se.mParentBounds))) { - return false; - } - - return mFqcn.equals(se.mFqcn) && - mAttributes.size() == se.mAttributes.size() && - mElements.size() == se.mElements.size() && - mAttributes.equals(se.mAttributes) && - mElements.equals(se.mElements); - } - return false; - } - - @Override - public int hashCode() { - long c = mFqcn.hashCode(); - // uses the formula defined in java.util.List.hashCode() - c = 31*c + mAttributes.hashCode(); - c = 31*c + mElements.hashCode(); - if (mParentFqcn != null) { - c = 31*c + mParentFqcn.hashCode(); - } - if (mBounds != null && mBounds.isValid()) { - c = 31*c + mBounds.hashCode(); - } - if (mParentBounds != null && mParentBounds.isValid()) { - c = 31*c + mParentBounds.hashCode(); - } - - if (c > 0x0FFFFFFFFL) { - // wrap any overflow - c = c ^ (c >> 32); - } - return (int)(c & 0x0FFFFFFFFL); - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleXmlTransfer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleXmlTransfer.java deleted file mode 100644 index 20ac2033e..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleXmlTransfer.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; - -import org.eclipse.swt.dnd.ByteArrayTransfer; -import org.eclipse.swt.dnd.TransferData; - -import java.io.UnsupportedEncodingException; - -/** - * A d'n'd {@link Transfer} class that can transfer a <em>simplified</em> XML fragment - * to transfer elements and their attributes between {@link LayoutCanvas}. - * <p/> - * The implementation is based on the {@link ByteArrayTransfer} and what we transfer - * is text with the following fixed format: - * <p/> - * <pre> - * {element-name element-property ... - * attrib_name="attrib_value" - * attrib2="..." - * {...inner elements... - * } - * } - * {...next element... - * } - * - * </pre> - * The format has nothing to do with XML per se, except for the fact that the - * transfered content represents XML elements and XML attributes. - * - * <p/> - * The detailed syntax is: - * <pre> - * - ELEMENT := {NAME PROPERTY*\nATTRIB_LINE*ELEMENT*}\n - * - PROPERTY := $[A-Z]=[^ ]* - * - NAME := [^\n=]+ - * - ATTRIB_LINE := @URI:NAME=[^\n]*\n - * </pre> - * - * Elements are represented by {@link SimpleElement}s and their attributes by - * {@link SimpleAttribute}s, all of which have very specific properties that are - * specifically limited to our needs for drag'n'drop. - */ -final class SimpleXmlTransfer extends ByteArrayTransfer { - - // Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html - - private static final String TYPE_NAME = "android.ADT.simple.xml.transfer.1"; //$NON-NLS-1$ - private static final int TYPE_ID = registerType(TYPE_NAME); - private static final SimpleXmlTransfer sInstance = new SimpleXmlTransfer(); - - /** Private constructor. Use {@link #getInstance()} to retrieve the singleton instance. */ - private SimpleXmlTransfer() { - // pass - } - - /** Returns the singleton instance. */ - public static SimpleXmlTransfer getInstance() { - return sInstance; - } - - /** - * Helper method that returns the FQCN transfered for the given {@link ElementDescriptor}. - * <p/> - * If the descriptor is a {@link ViewElementDescriptor}, the transfered data is the FQCN - * of the Android View class represented (e.g. "android.widget.Button"). - * For any other non-null descriptor, the XML name is used. - * Otherwise it is null. - * - * @param desc The {@link ElementDescriptor} to transfer. - * @return The FQCN, XML name or null. - */ - public static String getFqcn(ElementDescriptor desc) { - if (desc instanceof ViewElementDescriptor) { - return ((ViewElementDescriptor) desc).getFullClassName(); - } else if (desc != null) { - return desc.getXmlName(); - } - - return null; - } - - @Override - protected int[] getTypeIds() { - return new int[] { TYPE_ID }; - } - - @Override - protected String[] getTypeNames() { - return new String[] { TYPE_NAME }; - } - - /** Transforms a array of {@link SimpleElement} into a native data transfer. */ - @Override - protected void javaToNative(Object object, TransferData transferData) { - if (object == null || !(object instanceof SimpleElement[])) { - return; - } - - if (isSupportedType(transferData)) { - StringBuilder sb = new StringBuilder(); - for (SimpleElement e : (SimpleElement[]) object) { - sb.append(e.toString()); - } - String data = sb.toString(); - - try { - byte[] buf = data.getBytes("UTF-8"); //$NON-NLS-1$ - super.javaToNative(buf, transferData); - } catch (UnsupportedEncodingException e) { - // unlikely; ignore - } - } - } - - /** - * Recreates an array of {@link SimpleElement} from a native data transfer. - * - * @return An array of {@link SimpleElement} or null. The array may be empty. - */ - @Override - protected Object nativeToJava(TransferData transferData) { - if (isSupportedType(transferData)) { - byte[] buf = (byte[]) super.nativeToJava(transferData); - if (buf != null && buf.length > 0) { - try { - String s = new String(buf, "UTF-8"); //$NON-NLS-1$ - return SimpleElement.parseString(s); - } catch (UnsupportedEncodingException e) { - // unlikely to happen, but still possible - } - } - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java deleted file mode 100644 index 0923dda79..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java +++ /dev/null @@ -1,75 +0,0 @@ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IMenuCreator; -import org.eclipse.swt.events.MenuEvent; -import org.eclipse.swt.events.MenuListener; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.MenuItem; - -/** - * Action which creates a submenu that is dynamically populated by subclasses - */ -public abstract class SubmenuAction extends Action implements MenuListener, IMenuCreator { - private Menu mMenu; - - public SubmenuAction(String title) { - super(title, IAction.AS_DROP_DOWN_MENU); - } - - @Override - public IMenuCreator getMenuCreator() { - return this; - } - - @Override - public void dispose() { - if (mMenu != null) { - mMenu.dispose(); - mMenu = null; - } - } - - @Override - public Menu getMenu(Control parent) { - return null; - } - - @Override - public Menu getMenu(Menu parent) { - mMenu = new Menu(parent); - mMenu.addMenuListener(this); - return mMenu; - } - - @Override - public void menuHidden(MenuEvent e) { - } - - protected abstract void addMenuItems(Menu menu); - - @Override - 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(); - } - addMenuItems(mMenu); - } - - protected void addDisabledMessageItem(String message) { - IAction action = new Action(message, IAction.AS_PUSH_BUTTON) { - @Override - public void run() { - } - }; - action.setEnabled(false); - new ActionContributionItem(action).fill(mMenu, -1); - - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java deleted file mode 100644 index 93a33283c..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (C) 2010 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.common.api.DrawingStyle; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.RGB; - -/** - * Description of the drawing styles with specific color, line style and alpha - * definitions. This class corresponds to the more generic {@link DrawingStyle} - * class which defines the drawing styles but does not introduce any specific - * SWT values to the API clients. - * <p> - * TODO: This class should eventually be replaced by a scheme where the color - * constants are instead coming from the theme. - */ -public enum SwtDrawingStyle { - /** - * The style definition corresponding to {@link DrawingStyle#SELECTION} - */ - SELECTION(new RGB(0x00, 0x99, 0xFF), 192, new RGB(0x00, 0x99, 0xFF), 192, 1, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#GUIDELINE} - */ - GUIDELINE(new RGB(0x00, 0xAA, 0x00), 192, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#GUIDELINE} - */ - GUIDELINE_SHADOW(new RGB(0x00, 0xAA, 0x00), 192, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#GUIDELINE_DASHED} - */ - GUIDELINE_DASHED(new RGB(0x00, 0xAA, 0x00), 192, SWT.LINE_CUSTOM), - - /** - * The style definition corresponding to {@link DrawingStyle#DISTANCE} - */ - DISTANCE(new RGB(0xFF, 0x00, 0x00), 192 - 32, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#GRID} - */ - GRID(new RGB(0xAA, 0xAA, 0xAA), 128, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#HOVER} - */ - HOVER(null, 0, new RGB(0xFF, 0xFF, 0xFF), 40, 1, SWT.LINE_DOT), - - /** - * The style definition corresponding to {@link DrawingStyle#HOVER} - */ - HOVER_SELECTION(null, 0, new RGB(0xFF, 0xFF, 0xFF), 10, 1, SWT.LINE_DOT), - - /** - * The style definition corresponding to {@link DrawingStyle#ANCHOR} - */ - ANCHOR(new RGB(0x00, 0x99, 0xFF), 96, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#OUTLINE} - */ - OUTLINE(new RGB(0x88, 0xFF, 0x88), 160, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#DROP_RECIPIENT} - */ - DROP_RECIPIENT(new RGB(0xFF, 0x99, 0x00), 255, new RGB(0xFF, 0x99, 0x00), 160, 2, - SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#DROP_ZONE} - */ - DROP_ZONE(new RGB(0x00, 0xAA, 0x00), 220, new RGB(0x55, 0xAA, 0x00), 200, 1, SWT.LINE_SOLID), - - /** - * The style definition corresponding to - * {@link DrawingStyle#DROP_ZONE_ACTIVE} - */ - DROP_ZONE_ACTIVE(new RGB(0x00, 0xAA, 0x00), 220, new RGB(0x00, 0xAA, 0x00), 128, 2, - SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#DROP_PREVIEW} - */ - DROP_PREVIEW(new RGB(0xFF, 0x99, 0x00), 255, null, 0, 2, SWT.LINE_CUSTOM), - - /** - * The style definition corresponding to {@link DrawingStyle#RESIZE_PREVIEW} - */ - RESIZE_PREVIEW(new RGB(0xFF, 0x99, 0x00), 255, null, 0, 2, SWT.LINE_SOLID), - - /** - * The style used to show a proposed resize bound which is being rejected (for example, - * because there is no near edge to attach to in a RelativeLayout). - */ - RESIZE_FAIL(new RGB(0xFF, 0x99, 0x00), 255, null, 0, 2, SWT.LINE_CUSTOM), - - /** - * The style definition corresponding to {@link DrawingStyle#HELP} - */ - HELP(new RGB(0xFF, 0xFF, 0xFF), 255, new RGB(0x00, 0x00, 0x00), 128, 1, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#INVALID} - */ - INVALID(new RGB(0xFF, 0xFF, 0xFF), 255, new RGB(0xFF, 0x00, 0x00), 64, 2, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#DEPENDENCY} - */ - DEPENDENCY(new RGB(0xFF, 0xFF, 0xFF), 255, new RGB(0xFF, 0xFF, 0x00), 24, 2, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#CYCLE} - */ - CYCLE(new RGB(0xFF, 0x00, 0x00), 192, null, 0, 1, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#DRAGGED} - */ - DRAGGED(new RGB(0xFF, 0xFF, 0xFF), 255, new RGB(0x00, 0xFF, 0x00), 16, 2, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#EMPTY} - */ - EMPTY(new RGB(0xFF, 0xFF, 0x55), 255, new RGB(0xFF, 0xFF, 0x55), 255, 1, SWT.LINE_DASH), - - /** - * The style definition corresponding to {@link DrawingStyle#CUSTOM1} - */ - CUSTOM1(new RGB(0xFF, 0x00, 0xFF), 255, null, 0, 1, SWT.LINE_SOLID), - - /** - * The style definition corresponding to {@link DrawingStyle#CUSTOM2} - */ - CUSTOM2(new RGB(0x00, 0xFF, 0xFF), 255, null, 0, 1, SWT.LINE_DOT); - - /** - * Construct a new style value with the given foreground, background, width, - * linestyle and transparency. - * - * @param stroke A color descriptor for the foreground color, or null if no - * foreground color should be set - * @param fill A color descriptor for the background color, or null if no - * foreground color should be set - * @param lineWidth The line width, in pixels, or 0 if no line width should - * be set - * @param lineStyle The SWT line style - such as {@link SWT#LINE_SOLID}. - * @param strokeAlpha The alpha value of the stroke, an integer in the range 0 to 255 - * where 0 is fully transparent and 255 is fully opaque. - * @param fillAlpha The alpha value of the fill, an integer in the range 0 to 255 - * where 0 is fully transparent and 255 is fully opaque. - */ - private SwtDrawingStyle(RGB stroke, int strokeAlpha, RGB fill, int fillAlpha, int lineWidth, - int lineStyle) { - mStroke = stroke; - mFill = fill; - mLineWidth = lineWidth; - mLineStyle = lineStyle; - mStrokeAlpha = strokeAlpha; - mFillAlpha = fillAlpha; - } - - /** - * Convenience constructor for typical drawing styles, which do not specify - * a fill and use a standard thickness line - * - * @param stroke Stroke color to be used (e.g. for the border/foreground) - * @param strokeAlpha Transparency to use for the stroke; 0 is transparent - * and 255 is fully opaque. - * @param lineStyle The SWT line style - such as {@link SWT#LINE_SOLID}. - */ - private SwtDrawingStyle(RGB stroke, int strokeAlpha, int lineStyle) { - this(stroke, strokeAlpha, null, 255, 1, lineStyle); - } - - /** - * Return the stroke/foreground/border RGB color description to be used for - * this style, or null if none - */ - public RGB getStrokeColor() { - return mStroke; - } - - /** - * Return the fill/background/interior RGB color description to be used for - * this style, or null if none - */ - public RGB getFillColor() { - return mFill; - } - - /** Return the line width to be used for this style */ - public int getLineWidth() { - return mLineWidth; - } - - /** Return the SWT line style to be used for this style */ - public int getLineStyle() { - return mLineStyle; - } - - /** - * Return the stroke alpha value (in the range 0,255) to be used for this - * style - */ - public int getStrokeAlpha() { - return mStrokeAlpha; - } - - /** - * Return the fill alpha value (in the range 0,255) to be used for this - * style - */ - public int getFillAlpha() { - return mFillAlpha; - } - - /** - * Return the corresponding SwtDrawingStyle for the given - * {@link DrawingStyle} - * @param style The style to convert from a {@link DrawingStyle} to a {@link SwtDrawingStyle}. - * @return A corresponding {@link SwtDrawingStyle}. - */ - public static SwtDrawingStyle of(DrawingStyle style) { - switch (style) { - case SELECTION: - return SELECTION; - case GUIDELINE: - return GUIDELINE; - case GUIDELINE_SHADOW: - return GUIDELINE_SHADOW; - case GUIDELINE_DASHED: - return GUIDELINE_DASHED; - case DISTANCE: - return DISTANCE; - case GRID: - return GRID; - case HOVER: - return HOVER; - case HOVER_SELECTION: - return HOVER_SELECTION; - case ANCHOR: - return ANCHOR; - case OUTLINE: - return OUTLINE; - case DROP_ZONE: - return DROP_ZONE; - case DROP_ZONE_ACTIVE: - return DROP_ZONE_ACTIVE; - case DROP_RECIPIENT: - return DROP_RECIPIENT; - case DROP_PREVIEW: - return DROP_PREVIEW; - case RESIZE_PREVIEW: - return RESIZE_PREVIEW; - case RESIZE_FAIL: - return RESIZE_FAIL; - case HELP: - return HELP; - case INVALID: - return INVALID; - case DEPENDENCY: - return DEPENDENCY; - case CYCLE: - return CYCLE; - case DRAGGED: - return DRAGGED; - case EMPTY: - return EMPTY; - case CUSTOM1: - return CUSTOM1; - case CUSTOM2: - return CUSTOM2; - - // Internal error - default: - throw new IllegalArgumentException("Unknown style " + style); - } - } - - /** RGB description of the stroke/foreground/border color */ - private final RGB mStroke; - - /** RGB description of the fill/foreground/interior color */ - private final RGB mFill; - - /** Pixel thickness of the stroke/border */ - private final int mLineWidth; - - /** SWT line style of the border/stroke */ - private final int mLineStyle; - - /** Alpha (in the range 0-255) of the stroke/border */ - private final int mStrokeAlpha; - - /** Alpha (in the range 0-255) of the fill/interior */ - private final int mFillAlpha; -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java deleted file mode 100644 index 64e91bedf..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtils.java +++ /dev/null @@ -1,457 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE; - -import com.android.ide.common.api.Rect; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; - -import org.eclipse.swt.SWTException; -import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.FontMetrics; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.PaletteData; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; - -import java.awt.Graphics; -import java.awt.image.BufferedImage; -import java.awt.image.DataBuffer; -import java.awt.image.DataBufferByte; -import java.awt.image.DataBufferInt; -import java.awt.image.WritableRaster; -import java.util.List; - -/** - * Various generic SWT utilities such as image conversion. - */ -public class SwtUtils { - - private SwtUtils() { - } - - /** - * Returns the {@link PaletteData} describing the ARGB ordering expected from integers - * representing pixels for AWT {@link BufferedImage}. - * - * @param imageType the {@link BufferedImage#getType()} type - * @return A new {@link PaletteData} suitable for AWT images. - */ - public static PaletteData getAwtPaletteData(int imageType) { - switch (imageType) { - case BufferedImage.TYPE_INT_RGB: - case BufferedImage.TYPE_INT_ARGB: - case BufferedImage.TYPE_INT_ARGB_PRE: - return new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF); - - case BufferedImage.TYPE_3BYTE_BGR: - case BufferedImage.TYPE_4BYTE_ABGR: - case BufferedImage.TYPE_4BYTE_ABGR_PRE: - return new PaletteData(0x000000FF, 0x0000FF00, 0x00FF0000); - - default: - throw new UnsupportedOperationException("RGB type not supported yet."); - } - } - - /** - * Returns true if the given type of {@link BufferedImage} is supported for - * conversion. For unsupported formats, use - * {@link #convertToCompatibleFormat(BufferedImage)} first. - * - * @param imageType the {@link BufferedImage#getType()} - * @return true if we can convert the given buffered image format - */ - private static boolean isSupportedPaletteType(int imageType) { - switch (imageType) { - case BufferedImage.TYPE_INT_RGB: - case BufferedImage.TYPE_INT_ARGB: - case BufferedImage.TYPE_INT_ARGB_PRE: - case BufferedImage.TYPE_3BYTE_BGR: - case BufferedImage.TYPE_4BYTE_ABGR: - case BufferedImage.TYPE_4BYTE_ABGR_PRE: - return true; - default: - return false; - } - } - - /** Converts the given arbitrary {@link BufferedImage} to another {@link BufferedImage} - * in a format that is supported (see {@link #isSupportedPaletteType(int)}) - * - * @param image the image to be converted - * @return a new image that is in a guaranteed compatible format - */ - private static BufferedImage convertToCompatibleFormat(BufferedImage image) { - BufferedImage converted = new BufferedImage(image.getWidth(), image.getHeight(), - BufferedImage.TYPE_INT_ARGB); - Graphics graphics = converted.getGraphics(); - graphics.drawImage(image, 0, 0, null); - graphics.dispose(); - - return converted; - } - - /** - * Converts an AWT {@link BufferedImage} into an equivalent SWT {@link Image}. Whether - * the transparency data is transferred is optional, and this method can also apply an - * alpha adjustment during the conversion. - * <p/> - * Implementation details: on Windows, the returned {@link Image} will have an ordering - * matching the Windows DIB (e.g. RGBA, not ARGB). Callers must make sure to use - * <code>Image.getImageData().paletteData</code> to get the right pixels out of the image. - * - * @param display The display where the SWT image will be shown - * @param awtImage The AWT {@link BufferedImage} - * @param transferAlpha If true, copy alpha data out of the source image - * @param globalAlpha If -1, do nothing, otherwise adjust the alpha of the final image - * by the given amount in the range [0,255] - * @return A new SWT {@link Image} with the same contents as the source - * {@link BufferedImage} - */ - public static Image convertToSwt(Device display, BufferedImage awtImage, - boolean transferAlpha, int globalAlpha) { - if (!isSupportedPaletteType(awtImage.getType())) { - awtImage = convertToCompatibleFormat(awtImage); - } - - int width = awtImage.getWidth(); - int height = awtImage.getHeight(); - - WritableRaster raster = awtImage.getRaster(); - DataBuffer dataBuffer = raster.getDataBuffer(); - ImageData imageData = - new ImageData(width, height, 32, getAwtPaletteData(awtImage.getType())); - - if (dataBuffer instanceof DataBufferInt) { - int[] imageDataBuffer = ((DataBufferInt) dataBuffer).getData(); - imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0); - } else if (dataBuffer instanceof DataBufferByte) { - byte[] imageDataBuffer = ((DataBufferByte) dataBuffer).getData(); - try { - imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0); - } catch (SWTException se) { - // Unsupported depth - return convertToSwt(display, convertToCompatibleFormat(awtImage), - transferAlpha, globalAlpha); - } - } - - if (transferAlpha) { - byte[] alphaData = new byte[height * width]; - for (int y = 0; y < height; y++) { - byte[] alphaRow = new byte[width]; - for (int x = 0; x < width; x++) { - int alpha = awtImage.getRGB(x, y) >>> 24; - - // We have to multiply in the alpha now since if we - // set ImageData.alpha, it will ignore the alphaData. - if (globalAlpha != -1) { - alpha = alpha * globalAlpha >> 8; - } - - alphaRow[x] = (byte) alpha; - } - System.arraycopy(alphaRow, 0, alphaData, y * width, width); - } - - imageData.alphaData = alphaData; - } else if (globalAlpha != -1) { - imageData.alpha = globalAlpha; - } - - return new Image(display, imageData); - } - - /** - * Converts a direct-color model SWT image to an equivalent AWT image. If the image - * does not have a supported color model, returns null. This method does <b>NOT</b> - * preserve alpha in the source image. - * - * @param swtImage the SWT image to be converted to AWT - * @return an AWT image representing the source SWT image - */ - public static BufferedImage convertToAwt(Image swtImage) { - ImageData swtData = swtImage.getImageData(); - BufferedImage awtImage = - new BufferedImage(swtData.width, swtData.height, BufferedImage.TYPE_INT_ARGB); - PaletteData swtPalette = swtData.palette; - if (swtPalette.isDirect) { - PaletteData awtPalette = getAwtPaletteData(awtImage.getType()); - - if (swtPalette.equals(awtPalette)) { - // No color conversion needed. - for (int y = 0; y < swtData.height; y++) { - for (int x = 0; x < swtData.width; x++) { - int pixel = swtData.getPixel(x, y); - awtImage.setRGB(x, y, 0xFF000000 | pixel); - } - } - } else { - // We need to remap the colors - int sr = -awtPalette.redShift + swtPalette.redShift; - int sg = -awtPalette.greenShift + swtPalette.greenShift; - int sb = -awtPalette.blueShift + swtPalette.blueShift; - - for (int y = 0; y < swtData.height; y++) { - for (int x = 0; x < swtData.width; x++) { - int pixel = swtData.getPixel(x, y); - - int r = pixel & swtPalette.redMask; - int g = pixel & swtPalette.greenMask; - int b = pixel & swtPalette.blueMask; - r = (sr < 0) ? r >>> -sr : r << sr; - g = (sg < 0) ? g >>> -sg : g << sg; - b = (sb < 0) ? b >>> -sb : b << sb; - - pixel = 0xFF000000 | r | g | b; - awtImage.setRGB(x, y, pixel); - } - } - } - } else { - return null; - } - - return awtImage; - } - - /** - * Creates a new image from a source image where the contents from a given set of - * bounding boxes are copied into the new image and the rest is left transparent. A - * scale can be applied to make the resulting image larger or smaller than the source - * image. Note that the alpha channel in the original image is ignored, and the alpha - * values for the painted rectangles will be set to a specific value passed into this - * function. - * - * @param image the source image - * @param rectangles the set of rectangles (bounding boxes) to copy from the source - * image - * @param boundingBox the bounding rectangle of the rectangle list, which can be - * computed by {@link ImageUtils#getBoundingRectangle} - * @param scale a scale factor to apply to the result, e.g. 0.5 to shrink the - * destination down 50%, 1.0 to leave it alone and 2.0 to zoom in by - * doubling the image size - * @param alpha the alpha (in the range 0-255) that painted bits should be set to - * @return a pair of the rendered cropped image, and the location within the source - * image that the crop begins (multiplied by the scale). May return null if - * there are no selected items. - */ - public static Image drawRectangles(Image image, - List<Rectangle> rectangles, Rectangle boundingBox, double scale, byte alpha) { - - if (rectangles.size() == 0 || boundingBox == null || boundingBox.isEmpty()) { - return null; - } - - ImageData srcData = image.getImageData(); - int destWidth = (int) (scale * boundingBox.width); - int destHeight = (int) (scale * boundingBox.height); - - ImageData destData = new ImageData(destWidth, destHeight, srcData.depth, srcData.palette); - byte[] alphaData = new byte[destHeight * destWidth]; - destData.alphaData = alphaData; - - for (Rectangle bounds : rectangles) { - int dx1 = bounds.x - boundingBox.x; - int dy1 = bounds.y - boundingBox.y; - int dx2 = dx1 + bounds.width; - int dy2 = dy1 + bounds.height; - - dx1 *= scale; - dy1 *= scale; - dx2 *= scale; - dy2 *= scale; - - int sx1 = bounds.x; - int sy1 = bounds.y; - int sx2 = sx1 + bounds.width; - int sy2 = sy1 + bounds.height; - - if (scale == 1.0d) { - for (int dy = dy1, sy = sy1; dy < dy2; dy++, sy++) { - for (int dx = dx1, sx = sx1; dx < dx2; dx++, sx++) { - destData.setPixel(dx, dy, srcData.getPixel(sx, sy)); - alphaData[dy * destWidth + dx] = alpha; - } - } - } else { - // Scaled copy. - int sxDelta = sx2 - sx1; - int dxDelta = dx2 - dx1; - int syDelta = sy2 - sy1; - int dyDelta = dy2 - dy1; - for (int dy = dy1, sy = sy1; dy < dy2; dy++, sy = (dy - dy1) * syDelta / dyDelta - + sy1) { - for (int dx = dx1, sx = sx1; dx < dx2; dx++, sx = (dx - dx1) * sxDelta - / dxDelta + sx1) { - assert sx < sx2 && sy < sy2; - destData.setPixel(dx, dy, srcData.getPixel(sx, sy)); - alphaData[dy * destWidth + dx] = alpha; - } - } - } - } - - return new Image(image.getDevice(), destData); - } - - /** - * Creates a new empty/blank image of the given size - * - * @param display the display to associate the image with - * @param width the width of the image - * @param height the height of the image - * @return a new blank image of the given size - */ - public static Image createEmptyImage(Display display, int width, int height) { - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - return SwtUtils.convertToSwt(display, image, false, 0); - } - - /** - * Converts the given SWT {@link Rectangle} into an ADT {@link Rect} - * - * @param swtRect the SWT {@link Rectangle} - * @return an equivalent {@link Rect} - */ - public static Rect toRect(Rectangle swtRect) { - return new Rect(swtRect.x, swtRect.y, swtRect.width, swtRect.height); - } - - /** - * Sets the values of the given ADT {@link Rect} to the values of the given SWT - * {@link Rectangle} - * - * @param target the ADT {@link Rect} to modify - * @param source the SWT {@link Rectangle} to read values from - */ - public static void set(Rect target, Rectangle source) { - target.set(source.x, source.y, source.width, source.height); - } - - /** - * Compares an ADT {@link Rect} with an SWT {@link Rectangle} and returns true if they - * are equivalent - * - * @param r1 the ADT {@link Rect} - * @param r2 the SWT {@link Rectangle} - * @return true if the two rectangles are equivalent - */ - public static boolean equals(Rect r1, Rectangle r2) { - return r1.x == r2.x && r1.y == r2.y && r1.w == r2.width && r1.h == r2.height; - - } - - /** - * Get the average width of the font used by the given control - * - * @param display the display associated with the font usage - * @param font the font to look up the average character width for - * @return the average width, in pixels, of the given font - */ - public static final int getAverageCharWidth(Display display, Font font) { - GC gc = new GC(display); - gc.setFont(font); - FontMetrics fontMetrics = gc.getFontMetrics(); - int width = fontMetrics.getAverageCharWidth(); - gc.dispose(); - return width; - } - - /** - * Get the average width of the given font - * - * @param control the control to look up the default font for - * @return the average width, in pixels, of the current font in the control - */ - public static final int getAverageCharWidth(Control control) { - GC gc = new GC(control.getDisplay()); - int width = gc.getFontMetrics().getAverageCharWidth(); - gc.dispose(); - return width; - } - - /** - * Draws a drop shadow for the given rectangle into the given context. It - * will not draw anything if the rectangle is smaller than a minimum - * determined by the assets used to draw the shadow graphics. - * <p> - * This corresponds to {@link ImageUtils#drawRectangleShadow(Graphics, int, int, int, int)}, - * but applied directly to an SWT graphics context instead, such that no image conversion - * has to be performed. - * <p> - * Make sure to keep changes in the visual appearance here in sync with the - * AWT version in {@link ImageUtils#drawRectangleShadow(Graphics, int, int, int, int)}. - * - * @param gc the graphics context to draw into - * @param x the left coordinate of the left hand side of the rectangle - * @param y the top coordinate of the top of the rectangle - * @param width the width of the rectangle - * @param height the height of the rectangle - */ - public static final void drawRectangleShadow(GC gc, int x, int y, int width, int height) { - if (sShadowBottomLeft == null) { - IconFactory icons = IconFactory.getInstance(); - // See ImageUtils.drawRectangleShadow for an explanation of the assets. - sShadowBottomLeft = icons.getIcon("shadow-bl"); //$NON-NLS-1$ - sShadowBottom = icons.getIcon("shadow-b"); //$NON-NLS-1$ - sShadowBottomRight = icons.getIcon("shadow-br"); //$NON-NLS-1$ - sShadowRight = icons.getIcon("shadow-r"); //$NON-NLS-1$ - sShadowTopRight = icons.getIcon("shadow-tr"); //$NON-NLS-1$ - assert sShadowBottomRight.getImageData().width == SHADOW_SIZE; - assert sShadowBottomRight.getImageData().height == SHADOW_SIZE; - } - - ImageData bottomLeftData = sShadowBottomLeft.getImageData(); - ImageData topRightData = sShadowTopRight.getImageData(); - ImageData bottomData = sShadowBottom.getImageData(); - ImageData rightData = sShadowRight.getImageData(); - int blWidth = bottomLeftData.width; - int trHeight = topRightData.height; - if (width < blWidth) { - return; - } - if (height < trHeight) { - return; - } - - gc.drawImage(sShadowBottomLeft, x, y + height); - gc.drawImage(sShadowBottomRight, x + width, y + height); - gc.drawImage(sShadowTopRight, x + width, y); - gc.drawImage(sShadowBottom, - 0, 0, - bottomData.width, bottomData.height, - x + bottomLeftData.width, y + height, - width - bottomLeftData.width, bottomData.height); - gc.drawImage(sShadowRight, - 0, 0, - rightData.width, rightData.height, - x + width, y + topRightData.height, - rightData.width, height - topRightData.height); - } - - private static Image sShadowBottomLeft; - private static Image sShadowBottom; - private static Image sShadowBottomRight; - private static Image sShadowRight; - private static Image sShadowTopRight; -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java deleted file mode 100644 index d247e28d7..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java +++ /dev/null @@ -1,771 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.VIEW_MERGE; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.INode; -import com.android.ide.common.rendering.api.RenderSession; -import com.android.ide.common.rendering.api.ViewInfo; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.utils.Pair; - -import org.eclipse.swt.graphics.Rectangle; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.RandomAccess; -import java.util.Set; - -/** - * The view hierarchy class manages a set of view info objects and performs find - * operations on this set. - */ -public class ViewHierarchy { - private static final boolean DUMP_INFO = false; - - private LayoutCanvas mCanvas; - - /** - * Constructs a new {@link ViewHierarchy} tied to the given - * {@link LayoutCanvas}. - * - * @param canvas The {@link LayoutCanvas} to create a {@link ViewHierarchy} - * for. - */ - public ViewHierarchy(LayoutCanvas canvas) { - mCanvas = canvas; - } - - /** - * The CanvasViewInfo root created by the last call to {@link #setSession} - * with a valid layout. - * <p/> - * This <em>can</em> be null to indicate we're dealing with an empty document with - * no root node. Null here does not mean the result was invalid, merely that the XML - * had no content to display -- we need to treat an empty document as valid so that - * we can drop new items in it. - */ - private CanvasViewInfo mLastValidViewInfoRoot; - - /** - * True when the last {@link #setSession} provided a valid {@link LayoutScene}. - * <p/> - * When false this means the canvas is displaying an out-dated result image & bounds and some - * features should be disabled accordingly such a drag'n'drop. - * <p/> - * Note that an empty document (with a null {@link #mLastValidViewInfoRoot}) is considered - * valid since it is an acceptable drop target. - */ - private boolean mIsResultValid; - - /** - * A list of invisible parents (see {@link CanvasViewInfo#isInvisible()} for - * details) in the current view hierarchy. - */ - private final List<CanvasViewInfo> mInvisibleParents = new ArrayList<CanvasViewInfo>(); - - /** - * A read-only view of {@link #mInvisibleParents}; note that this is NOT a copy so it - * reflects updates to the underlying {@link #mInvisibleParents} list. - */ - private final List<CanvasViewInfo> mInvisibleParentsReadOnly = - Collections.unmodifiableList(mInvisibleParents); - - /** - * Flag which records whether or not we have any exploded parent nodes in this - * view hierarchy. This is used to track whether or not we need to recompute the - * layout when we exit show-all-invisible-parents mode (see - * {@link LayoutCanvas#showInvisibleViews}). - */ - private boolean mExplodedParents; - - /** - * Bounds of included views in the current view hierarchy when rendered in other context - */ - private List<Rectangle> mIncludedBounds; - - /** The render session for the current view hierarchy */ - private RenderSession mSession; - - /** Map from nodes to canvas view infos */ - private Map<UiViewElementNode, CanvasViewInfo> mNodeToView = Collections.emptyMap(); - - /** Map from DOM nodes to canvas view infos */ - private Map<Node, CanvasViewInfo> mDomNodeToView = Collections.emptyMap(); - - /** - * Disposes the view hierarchy content. - */ - public void dispose() { - if (mSession != null) { - mSession.dispose(); - mSession = null; - } - } - - - /** - * Sets the result of the layout rendering. The result object indicates if the layout - * rendering succeeded. If it did, it contains a bitmap and the objects rectangles. - * - * Implementation detail: the bridge's computeLayout() method already returns a newly - * allocated ILayourResult. That means we can keep this result and hold on to it - * when it is valid. - * - * @param session The new session, either valid or not. - * @param explodedNodes The set of individual nodes the layout computer was asked to - * explode. Note that these are independent of the explode-all mode where - * all views are exploded; this is used only for the mode ( - * {@link LayoutCanvas#showInvisibleViews}) where individual invisible - * nodes are padded during certain interactions. - */ - /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes, - boolean layoutlib5) { - // replace the previous scene, so the previous scene must be disposed. - if (mSession != null) { - mSession.dispose(); - } - - mSession = session; - mIsResultValid = (session != null && session.getResult().isSuccess()); - mExplodedParents = false; - mNodeToView = new HashMap<UiViewElementNode, CanvasViewInfo>(50); - if (mIsResultValid && session != null) { - List<ViewInfo> rootList = session.getRootViews(); - - Pair<CanvasViewInfo,List<Rectangle>> infos = null; - - if (rootList == null || rootList.size() == 0) { - // Special case: Look to see if this is really an empty <merge> view, - // which shows up without any ViewInfos in the merge. In that case we - // want to manufacture an empty view, such that we can target the view - // via drag & drop, etc. - if (hasMergeRoot()) { - ViewInfo mergeRoot = createMergeInfo(session); - infos = CanvasViewInfo.create(mergeRoot, layoutlib5); - } else { - infos = null; - } - } else { - if (rootList.size() > 1 && hasMergeRoot()) { - ViewInfo mergeRoot = createMergeInfo(session); - mergeRoot.setChildren(rootList); - infos = CanvasViewInfo.create(mergeRoot, layoutlib5); - } else { - ViewInfo root = rootList.get(0); - - if (root != null) { - infos = CanvasViewInfo.create(root, layoutlib5); - if (DUMP_INFO) { - dump(session, root, 0); - } - } else { - infos = null; - } - } - } - if (infos != null) { - mLastValidViewInfoRoot = infos.getFirst(); - mIncludedBounds = infos.getSecond(); - - if (mLastValidViewInfoRoot.getUiViewNode() == null && - mLastValidViewInfoRoot.getChildren().isEmpty()) { - GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor(); - if (editor.getIncludedWithin() != null) { - // Somehow, this view was supposed to be rendered within another - // view, yet this view was rendered as part of the other view. - // In that case, abort attempting to show included in; clear the - // include context and trigger a standalone re-render. - editor.showIn(null); - return; - } - } - - } else { - mLastValidViewInfoRoot = null; - mIncludedBounds = null; - } - - updateNodeProxies(mLastValidViewInfoRoot); - - // Update the data structures related to tracking invisible and exploded nodes. - // We need to find the {@link CanvasViewInfo} objects that correspond to - // the passed in {@link UiElementNode} keys that were re-rendered, and mark - // them as exploded and store them in a list for rendering. - mExplodedParents = false; - mInvisibleParents.clear(); - addInvisibleParents(mLastValidViewInfoRoot, explodedNodes); - - mDomNodeToView = new HashMap<Node, CanvasViewInfo>(mNodeToView.size()); - for (Map.Entry<UiViewElementNode, CanvasViewInfo> entry : mNodeToView.entrySet()) { - mDomNodeToView.put(entry.getKey().getXmlNode(), entry.getValue()); - } - - // Update the selection - mCanvas.getSelectionManager().sync(); - } else { - mIncludedBounds = null; - mInvisibleParents.clear(); - mDomNodeToView = Collections.emptyMap(); - } - } - - private ViewInfo createMergeInfo(RenderSession session) { - BufferedImage image = session.getImage(); - ControlPoint imageSize = ControlPoint.create(mCanvas, - mCanvas.getHorizontalTransform().getMargin() + image.getWidth(), - mCanvas.getVerticalTransform().getMargin() + image.getHeight()); - LayoutPoint layoutSize = imageSize.toLayout(); - UiDocumentNode model = mCanvas.getEditorDelegate().getUiRootNode(); - List<UiElementNode> children = model.getUiChildren(); - return new ViewInfo(VIEW_MERGE, children.get(0), 0, 0, layoutSize.x, layoutSize.y); - } - - /** - * Returns true if this view hierarchy corresponds to an editor that has a {@code - * <merge>} tag at the root - * - * @return true if there is a {@code <merge>} at the root of this editor's document - */ - private boolean hasMergeRoot() { - UiDocumentNode model = mCanvas.getEditorDelegate().getUiRootNode(); - if (model != null) { - List<UiElementNode> children = model.getUiChildren(); - if (children != null && children.size() > 0 - && VIEW_MERGE.equals(children.get(0).getDescriptor().getXmlName())) { - return true; - } - } - - return false; - } - - /** - * Creates or updates the node proxy for this canvas view info. - * <p/> - * Since proxies are reused, this will update the bounds of an existing proxy when the - * canvas is refreshed and a view changes position or size. - * <p/> - * This is a recursive call that updates the whole hierarchy starting at the given - * view info. - */ - private void updateNodeProxies(CanvasViewInfo vi) { - if (vi == null) { - return; - } - - UiViewElementNode key = vi.getUiViewNode(); - - if (key != null) { - mCanvas.getNodeFactory().create(vi); - mNodeToView.put(key, vi); - } - - for (CanvasViewInfo child : vi.getChildren()) { - updateNodeProxies(child); - } - } - - /** - * Make a pass over the view hierarchy and look for two things: - * <ol> - * <li>Invisible parents. These are nodes that can hold children and have empty - * bounds. These are then added to the {@link #mInvisibleParents} list. - * <li>Exploded nodes. These are nodes that were previously marked as invisible, and - * subsequently rendered by a recomputed layout. They now no longer have empty bounds, - * but should be specially marked via {@link CanvasViewInfo#setExploded} such that we - * for example in selection operations can determine if we need to recompute the - * layout. - * </ol> - * - * @param vi - * @param invisibleNodes - */ - private void addInvisibleParents(CanvasViewInfo vi, Set<UiElementNode> invisibleNodes) { - if (vi == null) { - return; - } - - if (vi.isInvisible()) { - mInvisibleParents.add(vi); - } else if (invisibleNodes != null) { - UiViewElementNode key = vi.getUiViewNode(); - - if (key != null && invisibleNodes.contains(key)) { - vi.setExploded(true); - mExplodedParents = true; - mInvisibleParents.add(vi); - } - } - - for (CanvasViewInfo child : vi.getChildren()) { - addInvisibleParents(child, invisibleNodes); - } - } - - /** - * Returns the current {@link RenderSession}. - * @return the session or null if none have been set. - */ - public RenderSession getSession() { - return mSession; - } - - /** - * Returns true when the last {@link #setSession} provided a valid - * {@link RenderSession}. - * <p/> - * When false this means the canvas is displaying an out-dated result image & bounds and some - * features should be disabled accordingly such a drag'n'drop. - * <p/> - * Note that an empty document (with a null {@link #getRoot()}) is considered - * valid since it is an acceptable drop target. - * @return True when this {@link ViewHierarchy} contains a valid hierarchy of views. - */ - public boolean isValid() { - return mIsResultValid; - } - - /** - * Returns true if the last valid content of the canvas represents an empty document. - * @return True if the last valid content of the canvas represents an empty document. - */ - public boolean isEmpty() { - return mLastValidViewInfoRoot == null; - } - - /** - * Returns true if we have parents in this hierarchy that are invisible (e.g. because - * they have no children and zero layout bounds). - * - * @return True if we have invisible parents. - */ - public boolean hasInvisibleParents() { - return mInvisibleParents.size() > 0; - } - - /** - * Returns true if we have views that were exploded during rendering - * @return True if we have exploded parents - */ - public boolean hasExplodedParents() { - return mExplodedParents; - } - - /** Locates and return any views that overlap the given selection rectangle. - * @param topLeft The top left corner of the selection rectangle. - * @param bottomRight The bottom right corner of the selection rectangle. - * @return A collection of {@link CanvasViewInfo} objects that overlap the - * rectangle. - */ - public Collection<CanvasViewInfo> findWithin( - LayoutPoint topLeft, - LayoutPoint bottomRight) { - Rectangle selectionRectangle = new Rectangle(topLeft.x, topLeft.y, bottomRight.x - - topLeft.x, bottomRight.y - topLeft.y); - List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(); - addWithin(mLastValidViewInfoRoot, selectionRectangle, infos); - return infos; - } - - /** - * Recursive internal version of {@link #findViewInfoAt(int, int)}. Please don't use directly. - * <p/> - * Tries to find the inner most child matching the given x,y coordinates in the view - * info sub-tree. This uses the potentially-expanded selection bounds. - * - * Returns null if not found. - */ - private void addWithin( - CanvasViewInfo canvasViewInfo, - Rectangle canvasRectangle, - List<CanvasViewInfo> infos) { - if (canvasViewInfo == null) { - return; - } - Rectangle r = canvasViewInfo.getSelectionRect(); - if (canvasRectangle.intersects(r)) { - - // try to find a matching child first - for (CanvasViewInfo child : canvasViewInfo.getChildren()) { - addWithin(child, canvasRectangle, infos); - } - - if (canvasViewInfo != mLastValidViewInfoRoot) { - infos.add(canvasViewInfo); - } - } - } - - /** - * Locates and returns the {@link CanvasViewInfo} corresponding to the given - * node, or null if it cannot be found. - * - * @param node The node we want to find a corresponding - * {@link CanvasViewInfo} for. - * @return The {@link CanvasViewInfo} corresponding to the given node, or - * null if no match was found. - */ - @Nullable - public CanvasViewInfo findViewInfoFor(@Nullable Node node) { - CanvasViewInfo vi = mDomNodeToView.get(node); - - if (vi == null) { - if (node == null) { - return null; - } else if (node.getNodeType() == Node.TEXT_NODE) { - return mDomNodeToView.get(node.getParentNode()); - } else if (node.getNodeType() == Node.ATTRIBUTE_NODE) { - return mDomNodeToView.get(((Attr) node).getOwnerElement()); - } else if (node.getNodeType() == Node.DOCUMENT_NODE) { - return mDomNodeToView.get(((Document) node).getDocumentElement()); - } - } - - return vi; - } - - /** - * Tries to find the inner most child matching the given x,y coordinates in - * the view info sub-tree, starting at the last know view info root. This - * uses the potentially-expanded selection bounds. - * <p/> - * Returns null if not found or if there's no view info root. - * - * @param p The point at which to look for the deepest match in the view - * hierarchy - * @return A {@link CanvasViewInfo} that intersects the given point, or null - * if nothing was found. - */ - public CanvasViewInfo findViewInfoAt(LayoutPoint p) { - if (mLastValidViewInfoRoot == null) { - return null; - } - - return findViewInfoAt_Recursive(p, mLastValidViewInfoRoot); - } - - /** - * Recursive internal version of {@link #findViewInfoAt(int, int)}. Please don't use directly. - * <p/> - * Tries to find the inner most child matching the given x,y coordinates in the view - * info sub-tree. This uses the potentially-expanded selection bounds. - * - * Returns null if not found. - */ - private CanvasViewInfo findViewInfoAt_Recursive(LayoutPoint p, CanvasViewInfo canvasViewInfo) { - if (canvasViewInfo == null) { - return null; - } - Rectangle r = canvasViewInfo.getSelectionRect(); - if (r.contains(p.x, p.y)) { - - // try to find a matching child first - // Iterate in REVERSE z order such that siblings on top - // are checked before earlier siblings (this matters in layouts like - // FrameLayout and in <merge> contexts where the views are sitting on top - // of each other and we want to select the same view as the one drawn - // on top of the others - List<CanvasViewInfo> children = canvasViewInfo.getChildren(); - assert children instanceof RandomAccess; - for (int i = children.size() - 1; i >= 0; i--) { - CanvasViewInfo child = children.get(i); - CanvasViewInfo v = findViewInfoAt_Recursive(p, child); - if (v != null) { - return v; - } - } - - // if no children matched, this is the view that we're looking for - return canvasViewInfo; - } - - return null; - } - - /** - * Returns a list of all the possible alternatives for a given view at the given - * position. This is used to build and manage the "alternate" selection that cycles - * around the parents or children of the currently selected element. - */ - /* package */ List<CanvasViewInfo> findAltViewInfoAt(LayoutPoint p) { - if (mLastValidViewInfoRoot != null) { - return findAltViewInfoAt_Recursive(p, mLastValidViewInfoRoot, null); - } - - return null; - } - - /** - * Internal recursive version of {@link #findAltViewInfoAt(int, int, CanvasViewInfo)}. - * Please don't use directly. - */ - private List<CanvasViewInfo> findAltViewInfoAt_Recursive( - LayoutPoint p, CanvasViewInfo parent, List<CanvasViewInfo> outList) { - Rectangle r; - - if (outList == null) { - outList = new ArrayList<CanvasViewInfo>(); - - if (parent != null) { - // add the parent root only once - r = parent.getSelectionRect(); - if (r.contains(p.x, p.y)) { - outList.add(parent); - } - } - } - - if (parent != null && !parent.getChildren().isEmpty()) { - // then add all children that match the position - for (CanvasViewInfo child : parent.getChildren()) { - r = child.getSelectionRect(); - if (r.contains(p.x, p.y)) { - outList.add(child); - } - } - - // finally recurse in the children - for (CanvasViewInfo child : parent.getChildren()) { - r = child.getSelectionRect(); - if (r.contains(p.x, p.y)) { - findAltViewInfoAt_Recursive(p, child, outList); - } - } - } - - return outList; - } - - /** - * Locates and returns the {@link CanvasViewInfo} corresponding to the given - * node, or null if it cannot be found. - * - * @param node The node we want to find a corresponding - * {@link CanvasViewInfo} for. - * @return The {@link CanvasViewInfo} corresponding to the given node, or - * null if no match was found. - */ - public CanvasViewInfo findViewInfoFor(INode node) { - return findViewInfoFor((NodeProxy) node); - } - - /** - * Tries to find a child with the same view key in the view info sub-tree. - * Returns null if not found. - * - * @param viewKey The view key that a matching {@link CanvasViewInfo} should - * have as its key. - * @return A {@link CanvasViewInfo} matching the given key, or null if not - * found. - */ - public CanvasViewInfo findViewInfoFor(UiElementNode viewKey) { - return mNodeToView.get(viewKey); - } - - /** - * Tries to find a child with the given node proxy as the view key. - * Returns null if not found. - * - * @param proxy The view key that a matching {@link CanvasViewInfo} should - * have as its key. - * @return A {@link CanvasViewInfo} matching the given key, or null if not - * found. - */ - @Nullable - public CanvasViewInfo findViewInfoFor(@Nullable NodeProxy proxy) { - if (proxy == null) { - return null; - } - return mNodeToView.get(proxy.getNode()); - } - - /** - * Returns a list of ALL ViewInfos (possibly excluding the root, depending - * on the parameter for that). - * - * @param includeRoot If true, include the root in the list, otherwise - * exclude it (but include all its children) - * @return A list of canvas view infos. - */ - public List<CanvasViewInfo> findAllViewInfos(boolean includeRoot) { - List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(); - if (mIsResultValid && mLastValidViewInfoRoot != null) { - findAllViewInfos(infos, mLastValidViewInfoRoot, includeRoot); - } - - return infos; - } - - private void findAllViewInfos(List<CanvasViewInfo> result, CanvasViewInfo canvasViewInfo, - boolean includeRoot) { - if (canvasViewInfo != null) { - if (includeRoot || !canvasViewInfo.isRoot()) { - result.add(canvasViewInfo); - } - for (CanvasViewInfo child : canvasViewInfo.getChildren()) { - findAllViewInfos(result, child, true); - } - } - } - - /** - * Returns the root of the view hierarchy, if any (could be null, for example - * on rendering failure). - * - * @return The current view hierarchy, or null - */ - public CanvasViewInfo getRoot() { - return mLastValidViewInfoRoot; - } - - /** - * Returns a collection of views that have zero bounds and that correspond to empty - * parents. Note that the views may not actually have zero bounds; in particular, if - * they are exploded ({@link CanvasViewInfo#isExploded()}, then they will have the - * bounds of a shown invisible node. Therefore, this method returns the views that - * would be invisible in a real rendering of the scene. - * - * @return A collection of empty parent views. - */ - public List<CanvasViewInfo> getInvisibleViews() { - return mInvisibleParentsReadOnly; - } - - /** - * Returns the invisible nodes (the {@link UiElementNode} objects corresponding - * to the {@link CanvasViewInfo} objects returned from {@link #getInvisibleViews()}. - * We are pulling out the nodes since they preserve their identity across layout - * rendering, and in particular we return it as a set such that the layout renderer - * can perform quick identity checks when looking up attribute values during the - * rendering process. - * - * @return A set of the invisible nodes. - */ - public Set<UiElementNode> getInvisibleNodes() { - if (mInvisibleParents.size() == 0) { - return Collections.emptySet(); - } - - Set<UiElementNode> nodes = new HashSet<UiElementNode>(mInvisibleParents.size()); - for (CanvasViewInfo info : mInvisibleParents) { - UiViewElementNode node = info.getUiViewNode(); - if (node != null) { - nodes.add(node); - } - } - - return nodes; - } - - /** - * Returns the list of bounds for included views in the current view hierarchy. Can be null - * when there are no included views. - * - * @return a list of included view bounds, or null - */ - public List<Rectangle> getIncludedBounds() { - return mIncludedBounds; - } - - /** - * Returns a map of the default properties for the given view object in this session - * - * @param viewObject the object to look up the properties map for - * @return the map of properties, or null if not found - */ - @Nullable - public Map<String, String> getDefaultProperties(@NonNull Object viewObject) { - if (mSession != null) { - return mSession.getDefaultProperties(viewObject); - } - - return null; - } - - /** - * Dumps a {@link ViewInfo} hierarchy to stdout - * - * @param session the corresponding session, if any - * @param info the {@link ViewInfo} object to dump - * @param depth the depth to indent it to - */ - public static void dump(RenderSession session, ViewInfo info, int depth) { - if (DUMP_INFO) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < depth; i++) { - sb.append(" "); //$NON-NLS-1$ - } - sb.append(info.getClassName()); - sb.append(" ["); //$NON-NLS-1$ - sb.append(info.getLeft()); - sb.append(","); //$NON-NLS-1$ - sb.append(info.getTop()); - sb.append(","); //$NON-NLS-1$ - sb.append(info.getRight()); - sb.append(","); //$NON-NLS-1$ - sb.append(info.getBottom()); - sb.append("]"); //$NON-NLS-1$ - Object cookie = info.getCookie(); - if (cookie instanceof UiViewElementNode) { - sb.append(" "); //$NON-NLS-1$ - UiViewElementNode node = (UiViewElementNode) cookie; - sb.append("<"); //$NON-NLS-1$ - sb.append(node.getDescriptor().getXmlName()); - sb.append(">"); //$NON-NLS-1$ - - String id = node.getAttributeValue(ATTR_ID); - if (id != null && !id.isEmpty()) { - sb.append(" "); - sb.append(id); - } - } else if (cookie != null) { - sb.append(" " + cookie); //$NON-NLS-1$ - } - /* Display defaults? - if (info.getViewObject() != null) { - Map<String, String> defaults = session.getDefaultProperties(info.getCookie()); - sb.append(" - defaults: "); //$NON-NLS-1$ - sb.append(defaults); - sb.append('\n'); - } - */ - - System.out.println(sb.toString()); - - for (ViewInfo child : info.getChildren()) { - dump(session, child, depth + 1); - } - } - } -} |