aboutsummaryrefslogtreecommitdiffstats
path: root/eclipse/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java69
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java32
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java29
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java370
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java60
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java1
6 files changed, 360 insertions, 201 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
index e245558..fd5198f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
@@ -23,6 +23,7 @@ import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescript
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.IUnknownDescriptorProvider;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle1.GraphicalLayoutEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle1.UiContentOutlinePage;
@@ -58,6 +59,8 @@ import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.w3c.dom.Document;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
/**
* Multi-page form editor for /res/layout XML files.
@@ -108,6 +111,10 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput,
return mUiRootNode;
}
+ public void setNewFileOnConfigChange(boolean state) {
+ mNewFileOnConfigChange = state;
+ }
+
// ---- Base Class Overrides ----
@Override
@@ -307,7 +314,7 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput,
/**
* Returns the custom IContentOutlinePage or IPropertySheetPage when asked for it.
*/
- @SuppressWarnings("rawtypes")
+ @SuppressWarnings({"rawtypes", "unchecked"})
@Override
public Object getAdapter(Class adapter) {
// for the outline, force it to come from the Graphical Editor.
@@ -572,7 +579,63 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput,
}
}
- public void setNewFileOnConfigChange(boolean state) {
- mNewFileOnConfigChange = state;
+ /**
+ * Helper method that returns a {@link ViewElementDescriptor} for the requested FQCN.
+ * Will return null if we can't find that FQCN or we lack the editor/data/descriptors info.
+ */
+ public ViewElementDescriptor getFqcnViewDescritor(String fqcn) {
+ AndroidTargetData data = getTargetData();
+ if (data != null) {
+ LayoutDescriptors layoutDesc = data.getLayoutDescriptors();
+ if (layoutDesc != null) {
+ DocumentDescriptor docDesc = layoutDesc.getDescriptor();
+ if (docDesc != null) {
+ return internalFindFqcnViewDescritor(fqcn, docDesc.getChildren(), null);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Internal helper to recursively search for a {@link ViewElementDescriptor} that matches
+ * the requested FQCN.
+ *
+ * @param fqcn The target View FQCN to find.
+ * @param descriptors A list of cildren descriptors to iterate through.
+ * @param visited A set we use to remember which descriptors have already been visited,
+ * necessary since the view descriptor hierarchy is cyclic.
+ * @return Either a matching {@link ViewElementDescriptor} or null.
+ */
+ private ViewElementDescriptor internalFindFqcnViewDescritor(String fqcn,
+ ElementDescriptor[] descriptors,
+ Set<ElementDescriptor> visited) {
+ if (visited == null) {
+ visited = new HashSet<ElementDescriptor>();
+ }
+
+ if (descriptors != null) {
+ for (ElementDescriptor desc : descriptors) {
+ if (visited.add(desc)) {
+ // Set.add() returns true if this a new element that was added to the set.
+ // That means we haven't visited this descriptor yet.
+ // We want a ViewElementDescriptor with a matching FQCN.
+ if (desc instanceof ViewElementDescriptor &&
+ fqcn.equals(((ViewElementDescriptor) desc).getFullClassName())) {
+ return (ViewElementDescriptor) desc;
+ }
+
+ // Visit its children
+ ViewElementDescriptor vd =
+ internalFindFqcnViewDescritor(fqcn, desc.getChildren(), visited);
+ if (vd != null) {
+ return vd;
+ }
+ }
+ }
+ }
+
+ return null;
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java
index ee0b802..e3a433c 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java
@@ -235,12 +235,6 @@ import java.util.Arrays;
public void drop(DropTargetEvent event) {
AdtPlugin.printErrorToConsole("DEBUG", "dropped");
- if (mTargetNode == null) {
- // DEBUG
- AdtPlugin.printErrorToConsole("DEBUG", "dropped on null targetNode");
- return;
- }
-
SimpleElement[] elements = null;
SimpleXmlTransfer sxt = SimpleXmlTransfer.getInstance();
@@ -260,6 +254,17 @@ import java.util.Arrays;
elements = mCurrentDragElements;
}
+ if (mTargetNode == null) {
+ if (mCanvas.isResultValid() && mCanvas.isEmptyDocument()) {
+ // There is no target node because the drop happens on an empty document.
+ // Attempt to create a root node accordingly.
+ createDocumentRoot(elements);
+ } else {
+ AdtPlugin.printErrorToConsole("DEBUG", "dropped on null targetNode");
+ }
+ return;
+ }
+
Point where = mCanvas.displayToCanvasPoint(event.x, event.y);
updateDropFeedback(mFeedback, event);
@@ -490,4 +495,19 @@ import java.util.Arrays;
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;
+ }
+
+ String rootFqcn = elements[0].getFqcn();
+
+ mCanvas.createDocumentRoot(rootFqcn);
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index ea3171f..5a4761d 100755
--- 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
@@ -83,6 +83,7 @@ import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.part.FileEditorInput;
+import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -945,7 +946,33 @@ public class GraphicalEditorPart extends EditorPart
UiDocumentNode model = getModel();
if (model.getUiChildren().size() == 0) {
- displayError("No Xml content. Go to the Outline view and add nodes.");
+ displayError("No XML content. Please add a root view or layout to your document.");
+
+ // 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 ILayoutResult that has no image,
+ // no root view yet indicates success and then update the canvas with it.
+
+ ILayoutResult result = new ILayoutResult() {
+ public String getErrorMessage() {
+ return null;
+ }
+
+ public BufferedImage getImage() {
+ return null;
+ }
+
+ public ILayoutViewInfo getRootView() {
+ return null;
+ }
+
+ public int getSuccess() {
+ return ILayoutResult.SUCCESS;
+ }
+ };
+
+ mCanvasViewer.getCanvas().setResult(result);
return;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index 6a946bd..d01aa71 100755
--- 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
@@ -22,13 +22,19 @@ import com.android.ide.eclipse.adt.editors.layout.gscripts.Point;
import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
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.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.layoutlib.api.ILayoutResult;
+import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
+import com.android.sdklib.SdkConstants;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.action.Action;
@@ -83,10 +89,11 @@ import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.actions.ActionFactory;
-import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
import org.eclipse.ui.actions.ContributionItemFactory;
import org.eclipse.ui.actions.TextActionHandler;
+import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
+import org.eclipse.ui.internal.registry.ViewDescriptor;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
@@ -143,28 +150,25 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
/** SWT clipboard instance. */
private Clipboard mClipboard;
- /*
- * The last valid ILayoutResult passed to {@link #setResult(ILayoutResult)}.
- * This can be null.
- * When non null, {@link #mLastValidViewInfoRoot} is guaranteed to be non-null too.
- */
- private ILayoutResult mLastValidResult;
-
/**
- * The CanvasViewInfo root created for the last update of {@link #mLastValidResult}.
- * This is null when {@link #mLastValidResult} is null.
- * When non null, {@link #mLastValidResult} is guaranteed to be non-null too.
+ * The CanvasViewInfo root created by the last call to {@link #setResult(ILayoutResult)}
+ * 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 #setResult(ILayoutResult)} provided a valid {@link ILayoutResult}
- * in which case it is also available in {@link #mLastValidResult}.
+ * True when the last {@link #setResult(ILayoutResult)} provided a valid {@link ILayoutResult}.
+ * <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/>
- * When this is false, {@link #mLastValidResult} can be non-null and points to an older
- * layout result.
+ * Note that an empty document (with a null {@link #mLastValidViewInfoRoot}) is considered
+ * valid since it is an acceptable drop target.
*/
private boolean mIsResultValid;
@@ -371,17 +375,25 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
/**
* Returns true when the last {@link #setResult(ILayoutResult)} provided a valid
- * {@link ILayoutResult} in which case it is also available in {@link #mLastValidResult}.
+ * {@link ILayoutResult}.
+ * <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/>
- * When this is false, {@link #mLastValidResult} can be non-null and points to an older
- * layout result.
- */
+ * Note that an empty document (with a null {@link #mLastValidViewInfoRoot}) is considered
+ * valid since it is an acceptable drop target.
+ */
/* package */ boolean isResultValid() {
return mIsResultValid;
}
+ /**
+ * Returns true if the last valid content of the canvas represents an empty document.
+ */
+ /* package */ boolean isEmptyDocument() {
+ return mLastValidViewInfoRoot == null;
+ }
+
/** Returns the Groovy Rules Engine, associated with the current project. */
/* package */ RulesEngine getRulesEngine() {
return mRulesEngine;
@@ -433,8 +445,12 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
mIsResultValid = (result != null && result.getSuccess() == ILayoutResult.SUCCESS);
if (mIsResultValid && result != null) {
- mLastValidResult = result;
- mLastValidViewInfoRoot = new CanvasViewInfo(result.getRootView());
+ ILayoutViewInfo root = result.getRootView();
+ if (root == null) {
+ mLastValidViewInfoRoot = null;
+ } else {
+ mLastValidViewInfoRoot = new CanvasViewInfo(result.getRootView());
+ }
setImage(result.getImage());
updateNodeProxies(mLastValidViewInfoRoot);
@@ -462,8 +478,10 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
// remove the current alternate selection views
mAltSelection = null;
- mHScale.setSize(mImage.getImageData().width, getClientArea().width);
- mVScale.setSize(mImage.getImageData().height, getClientArea().height);
+ if (mImage != null) {
+ mHScale.setSize(mImage.getImageData().width, getClientArea().width);
+ mVScale.setSize(mImage.getImageData().height, getClientArea().height);
+ }
// Pre-load the android.view.View rule in the Rules Engine. Doing it here means
// it will be done after the first rendering is finished. Successive calls are
@@ -787,7 +805,6 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
* view info.
*/
private void updateNodeProxies(CanvasViewInfo vi) {
-
if (vi == null) {
return;
}
@@ -806,26 +823,34 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
/**
* Sets the image of the last *successful* rendering.
* Converts the AWT image into an SWT image.
+ * <p/>
+ * The image *can* be null, which is the case when we are dealing with an empty document.
*/
private void setImage(BufferedImage awtImage) {
- int width = awtImage.getWidth();
- int height = awtImage.getHeight();
+ if (awtImage == null) {
+ mImage = null;
- Raster raster = awtImage.getData(new java.awt.Rectangle(width, height));
- int[] imageDataBuffer = ((DataBufferInt)raster.getDataBuffer()).getData();
+ } else {
+ int width = awtImage.getWidth();
+ int height = awtImage.getHeight();
- ImageData imageData = new ImageData(width, height, 32,
- new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF));
+ Raster raster = awtImage.getData(new java.awt.Rectangle(width, height));
+ int[] imageDataBuffer = ((DataBufferInt)raster.getDataBuffer()).getData();
- imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);
+ ImageData imageData = new ImageData(width, height, 32,
+ new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF));
- mImage = new Image(getDisplay(), imageData);
+ imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);
+
+ mImage = new Image(getDisplay(), imageData);
+ }
}
/**
* Sets the alpha for the given GC.
* <p/>
- * Alpha may not work on all platforms and may fail with an exception.
+ * 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.
@@ -845,7 +870,8 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
/**
* Sets the non-text antialias flag for the given GC.
* <p/>
- * Antialias may not work on all platforms and may fail with an exception.
+ * 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}.
@@ -969,37 +995,35 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
* Hover on top of a known child.
*/
private void onMouseMove(MouseEvent e) {
- if (mLastValidResult != null) {
- CanvasViewInfo root = mLastValidViewInfoRoot;
+ CanvasViewInfo root = mLastValidViewInfoRoot;
- int x = mHScale.inverseTranslate(e.x);
- int y = mVScale.inverseTranslate(e.y);
+ int x = mHScale.inverseTranslate(e.x);
+ int y = mVScale.inverseTranslate(e.y);
- CanvasViewInfo vi = findViewInfoAt(x, y);
+ CanvasViewInfo vi = findViewInfoAt(x, y);
- // We don't hover on the root since it's not a widget per see and it is always there.
- if (vi == root) {
- vi = null;
- }
+ // We don't hover on the root since it's not a widget per see and it is always there.
+ if (vi == root) {
+ vi = null;
+ }
- boolean needsUpdate = vi != mHoverViewInfo;
- mHoverViewInfo = vi;
+ boolean needsUpdate = vi != mHoverViewInfo;
+ mHoverViewInfo = vi;
- if (vi == null) {
- mHoverRect = null;
- } else {
- Rectangle r = vi.getSelectionRect();
- mHoverRect = new Rectangle(r.x, r.y, r.width, r.height);
- }
+ if (vi == null) {
+ mHoverRect = null;
+ } else {
+ Rectangle r = vi.getSelectionRect();
+ mHoverRect = new Rectangle(r.x, r.y, r.width, r.height);
+ }
- if (needsUpdate) {
- redraw();
- }
+ if (needsUpdate) {
+ redraw();
}
}
private void onMouseDown(MouseEvent e) {
- // pass, not used yet.
+ // Pass, not used yet. We do everything on mouse up.
}
/**
@@ -1010,81 +1034,78 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
* pointed at (i.e. click on an object then alt-click to cycle).
*/
private void onMouseUp(MouseEvent e) {
- if (mLastValidResult != null) {
+ boolean isShift = (e.stateMask & SWT.SHIFT) != 0;
+ boolean isAlt = (e.stateMask & SWT.ALT) != 0;
- boolean isShift = (e.stateMask & SWT.SHIFT) != 0;
- boolean isAlt = (e.stateMask & SWT.ALT) != 0;
+ int x = mHScale.inverseTranslate(e.x);
+ int y = mVScale.inverseTranslate(e.y);
- int x = mHScale.inverseTranslate(e.x);
- int y = mVScale.inverseTranslate(e.y);
+ CanvasViewInfo vi = findViewInfoAt(x, y);
- CanvasViewInfo vi = findViewInfoAt(x, y);
+ if (isShift && !isAlt) {
+ // Case where shift is pressed: pointed object is toggled.
- if (isShift && !isAlt) {
- // 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.
+ // reset alternate selection if any
+ mAltSelection = null;
- if (vi != null) {
- // toggle this selection on-off: remove it if already selected
- if (deselect(vi)) {
- redraw();
- return;
- }
+ // If nothing has been found at the cursor, assume it might be a user error
+ // and avoid clearing the existing selection.
- // otherwise add it.
- mSelections.add(new CanvasSelection(vi, mRulesEngine, mNodeFactory));
- fireSelectionChanged();
+ if (vi != null) {
+ // toggle this selection on-off: remove it if already selected
+ if (deselect(vi)) {
redraw();
+ return;
}
- } else if (isAlt) {
- // Case where alt is pressed: select or cycle the object pointed at.
+ // otherwise add it.
+ mSelections.add(new CanvasSelection(vi, mRulesEngine, mNodeFactory));
+ fireSelectionChanged();
+ redraw();
+ }
- // Note: if shift and alt are pressed, shift is ignored. The alternate selection
- // mechanism does not reset the current multiple selection unless they intersect.
+ } else if (isAlt) {
+ // Case where alt is pressed: select or cycle the object pointed at.
- // 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, findAltViewInfoAt(
- x, y, mLastValidViewInfoRoot, null));
+ // Note: if shift and alt are pressed, shift is ignored. The alternate selection
+ // mechanism does not reset the current multiple selection unless they intersect.
- // deselect them all, in case they were partially selected
- deselectAll(mAltSelection.getAltViews());
+ // 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, findAltViewInfoAt(x, y, mLastValidViewInfoRoot));
- // select the current one
- CanvasViewInfo vi2 = mAltSelection.getCurrent();
- if (vi2 != null) {
- mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine, mNodeFactory));
- 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(new CanvasSelection(vi2, mRulesEngine, mNodeFactory));
- fireSelectionChanged();
- }
- }
- redraw();
+ // 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(new CanvasSelection(vi2, mRulesEngine, mNodeFactory));
+ fireSelectionChanged();
+ }
} else {
- // Case where no modifier is pressed: either select or reset the selection.
-
- selectSingle(vi);
+ // 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(new CanvasSelection(vi2, mRulesEngine, mNodeFactory));
+ fireSelectionChanged();
+ }
}
+ redraw();
+
+ } else {
+ // Case where no modifier is pressed: either select or reset the selection.
+
+ selectSingle(vi);
}
}
@@ -1157,6 +1178,9 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
* Returns null if not found.
*/
private CanvasViewInfo findViewInfoKey(Object viewKey, CanvasViewInfo canvasViewInfo) {
+ if (canvasViewInfo == null) {
+ return null;
+ }
if (canvasViewInfo.getUiViewKey() == viewKey) {
return canvasViewInfo;
}
@@ -1174,33 +1198,38 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
/**
- * 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.
+ * 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.
- *
- * Returns null if not found or if there's view info root.
+ * <p/>
+ * Returns null if not found or if there's no view info root.
*/
/* package */ CanvasViewInfo findViewInfoAt(int x, int y) {
if (mLastValidViewInfoRoot == null) {
return null;
} else {
- return findViewInfoAt(x, y, mLastValidViewInfoRoot);
+ return findViewInfoAt_Recursive(x, y, 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(int x, int y, CanvasViewInfo canvasViewInfo) {
+ private CanvasViewInfo findViewInfoAt_Recursive(int x, int y, CanvasViewInfo canvasViewInfo) {
+ if (canvasViewInfo == null) {
+ return null;
+ }
Rectangle r = canvasViewInfo.getSelectionRect();
if (r.contains(x, y)) {
// try to find a matching child first
for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
- CanvasViewInfo v = findViewInfoAt(x, y, child);
+ CanvasViewInfo v = findViewInfoAt_Recursive(x, y, child);
if (v != null) {
return v;
}
@@ -1213,21 +1242,36 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
return null;
}
- private ArrayList<CanvasViewInfo> findAltViewInfoAt(
- int x, int y, CanvasViewInfo parent, ArrayList<CanvasViewInfo> outList) {
+ /**
+ * 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.
+ */
+ private List<CanvasViewInfo> findAltViewInfoAt(int x, int y, CanvasViewInfo parent) {
+ return findAltViewInfoAt_Recursive(x, y, parent, null);
+ }
+
+ /**
+ * Internal recursive version of {@link #findAltViewInfoAt(int, int, CanvasViewInfo)}.
+ * Please don't use directly.
+ */
+ private List<CanvasViewInfo> findAltViewInfoAt_Recursive(
+ int x, int y, CanvasViewInfo parent, List<CanvasViewInfo> outList) {
Rectangle r;
if (outList == null) {
outList = new ArrayList<CanvasViewInfo>();
- // add the parent root only once
- r = parent.getSelectionRect();
- if (r.contains(x, y)) {
- outList.add(parent);
+ if (parent != null) {
+ // add the parent root only once
+ r = parent.getSelectionRect();
+ if (r.contains(x, y)) {
+ outList.add(parent);
+ }
}
}
- if (!parent.getChildren().isEmpty()) {
+ if (parent != null && !parent.getChildren().isEmpty()) {
// then add all children that match the position
for (CanvasViewInfo child : parent.getChildren()) {
r = child.getSelectionRect();
@@ -1240,7 +1284,7 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
for (CanvasViewInfo child : parent.getChildren()) {
r = child.getSelectionRect();
if (r.contains(x, y)) {
- findAltViewInfoAt(x, y, child, outList);
+ findAltViewInfoAt_Recursive(x, y, child, outList);
}
}
}
@@ -1255,9 +1299,11 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
* selection list.
*/
private void selectAllViewInfos(CanvasViewInfo canvasViewInfo) {
- mSelections.add(new CanvasSelection(canvasViewInfo, mRulesEngine, mNodeFactory));
- for (CanvasViewInfo vi : canvasViewInfo.getChildren()) {
- selectAllViewInfos(vi);
+ if (canvasViewInfo != null) {
+ mSelections.add(new CanvasSelection(canvasViewInfo, mRulesEngine, mNodeFactory));
+ for (CanvasViewInfo vi : canvasViewInfo.getChildren()) {
+ selectAllViewInfos(vi);
+ }
}
}
@@ -1799,7 +1845,7 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
mAltSelection = null;
// Now select everything if there's a valid layout
- if (mIsResultValid && mLastValidResult != null) {
+ if (mIsResultValid && mLastValidViewInfoRoot != null) {
selectAllViewInfos(mLastValidViewInfoRoot);
redraw();
}
@@ -1940,4 +1986,62 @@ class LayoutCanvas extends Canvas implements ISelectionProvider {
// Must also be able to copy/paste into an empty document (prolly need to bypass script, and deal with the xmlns)
AdtPlugin.displayWarning("Canvas Paste", "Not implemented yet");
}
+
+ /**
+ * 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 #onPaste()} or
+ * by {@link CanvasDropListener#drop(org.eclipse.swt.dnd.DropTargetEvent)}.
+ *
+ * @param rootFqcn A non-null non-empty FQCN that must match an existing {@link ViewDescriptor}
+ * to add as root to the current empty XML document.
+ */
+ /* package */ void createDocumentRoot(String rootFqcn) {
+
+ // Need a valid empty document to create the new root
+ final UiDocumentNode uiDoc = mLayoutEditor.getUiRootNode();
+ if (uiDoc == null || uiDoc.getUiChildren().size() > 0) {
+ return;
+ }
+
+ // Find the view descriptor matching our FQCN
+ final ViewElementDescriptor viewDesc = mLayoutEditor.getFqcnViewDescritor(rootFqcn);
+ if (viewDesc == null) {
+ 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);
+
+ mLayoutEditor.wrapUndoRecording(title, new Runnable() {
+ public void run() {
+ mLayoutEditor.editXmlModel(new Runnable() {
+ public void run() {
+ UiElementNode uiNew = uiDoc.appendNewUiChild(viewDesc);
+
+ // A root node requires the Android XMLNS
+ uiNew.setAttributeValue(
+ "android",
+ XmlnsAttributeDescriptor.XMLNS_URI,
+ SdkConstants.NS_RESOURCES,
+ true /*override*/);
+
+ // Adjust the attributes
+ DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/);
+
+ uiNew.createXmlNode();
+ }
+ });
+ }
+ });
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java
index 052e2ab..31e42b8 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java
@@ -23,17 +23,13 @@ import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
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.LayoutEditor;
-import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SimpleAttribute;
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.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import org.eclipse.swt.graphics.Rectangle;
import org.w3c.dom.NamedNodeMap;
@@ -42,9 +38,7 @@ import org.w3c.dom.Node;
import groovy.lang.Closure;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
/**
*
@@ -349,58 +343,8 @@ public class NodeProxy implements INode {
*/
private ViewElementDescriptor getFqcnViewDescritor(String fqcn) {
AndroidXmlEditor editor = mNode.getEditor();
- if (editor != null) {
- AndroidTargetData data = editor.getTargetData();
- if (data != null) {
- LayoutDescriptors layoutDesc = data.getLayoutDescriptors();
- if (layoutDesc != null) {
- DocumentDescriptor docDesc = layoutDesc.getDescriptor();
- if (docDesc != null) {
- return internalFindFqcnViewDescritor(fqcn, docDesc.getChildren(), null);
- }
- }
- }
- }
-
- return null;
- }
-
- /**
- * Internal helper to recursively search for a {@link ViewElementDescriptor} that matches
- * the requested FQCN.
- *
- * @param fqcn The target View FQCN to find.
- * @param descriptors A list of cildren descriptors to iterate through.
- * @param visited A set we use to remember which descriptors have already been visited,
- * necessary since the view descriptor hierarchy is cyclic.
- * @return Either a matching {@link ViewElementDescriptor} or null.
- */
- private ViewElementDescriptor internalFindFqcnViewDescritor(String fqcn,
- ElementDescriptor[] descriptors,
- Set<ElementDescriptor> visited) {
- if (visited == null) {
- visited = new HashSet<ElementDescriptor>();
- }
-
- if (descriptors != null) {
- for (ElementDescriptor desc : descriptors) {
- if (visited.add(desc)) {
- // Set.add() returns true if this a new element that was added to the set.
- // That means we haven't visited this descriptor yet.
- // We want a ViewElementDescriptor with a matching FQCN.
- if (desc instanceof ViewElementDescriptor &&
- fqcn.equals(((ViewElementDescriptor) desc).getFullClassName())) {
- return (ViewElementDescriptor) desc;
- }
-
- // Visit its children
- ViewElementDescriptor vd =
- internalFindFqcnViewDescritor(fqcn, desc.getChildren(), visited);
- if (vd != null) {
- return vd;
- }
- }
- }
+ if (editor instanceof LayoutEditor) {
+ return ((LayoutEditor) editor).getFqcnViewDescritor(fqcn);
}
return null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
index 280f518..9d0927a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
@@ -1190,6 +1190,7 @@ public class UiElementNode implements IPropertySource {
new AttributeInfo(xmlAttrLocalName, new Format[] { Format.STRING } )
);
UiAttributeNode uiAttr = desc.createUiNode(this);
+ uiAttr.setDirty(true);
mUnknownUiAttributes.add(uiAttr);
return uiAttr;
}