blob: 80a23c6dbf43dc45d3ea28506894c4e135cc435e [file] [log] [blame]
/*
* 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.common.layout;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN;
import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
import static com.android.SdkConstants.ATTR_LAYOUT_ROW;
import static com.android.SdkConstants.ATTR_ORIENTATION;
import static com.android.SdkConstants.FQCN_GRID_LAYOUT;
import static com.android.SdkConstants.FQCN_SPACE;
import static com.android.SdkConstants.FQCN_SPACE_V7;
import static com.android.SdkConstants.GRAVITY_VALUE_FILL;
import static com.android.SdkConstants.GRAVITY_VALUE_FILL_HORIZONTAL;
import static com.android.SdkConstants.GRAVITY_VALUE_FILL_VERTICAL;
import static com.android.SdkConstants.GRAVITY_VALUE_LEFT;
import static com.android.SdkConstants.GRID_LAYOUT;
import static com.android.SdkConstants.VALUE_HORIZONTAL;
import static com.android.SdkConstants.VALUE_TRUE;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.api.DrawingStyle;
import com.android.ide.common.api.DropFeedback;
import com.android.ide.common.api.IDragElement;
import com.android.ide.common.api.IFeedbackPainter;
import com.android.ide.common.api.IGraphics;
import com.android.ide.common.api.IMenuCallback;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.INodeHandler;
import com.android.ide.common.api.IViewMetadata;
import com.android.ide.common.api.IViewMetadata.FillPreference;
import com.android.ide.common.api.IViewRule;
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.common.api.RuleAction;
import com.android.ide.common.api.RuleAction.Choices;
import com.android.ide.common.api.SegmentType;
import com.android.ide.common.layout.grid.GridDropHandler;
import com.android.ide.common.layout.grid.GridLayoutPainter;
import com.android.ide.common.layout.grid.GridModel;
import com.android.ide.common.layout.grid.GridModel.ViewData;
import com.android.utils.Pair;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* An {@link IViewRule} for android.widget.GridLayout which provides designtime
* interaction with GridLayouts.
* <p>
* TODO:
* <ul>
* <li>Handle multi-drag: preserving relative positions and alignments among dragged
* views.
* <li>Handle GridLayouts that have been configured in a vertical orientation.
* <li>Handle free-form editing GridLayouts that have been manually edited rather than
* built up using free-form editing (e.g. they might not follow the same spacing
* convention, might use weights etc)
* <li>Avoid setting row and column numbers on the actual elements if they can be skipped
* to make the XML leaner.
* </ul>
*/
public class GridLayoutRule extends BaseLayoutRule {
/**
* The size of the visual regular grid that we snap to (if {@link #sSnapToGrid} is set
*/
public static final int GRID_SIZE = 16;
/** Standard gap between views */
public static final int SHORT_GAP_DP = 16;
/**
* The preferred margin size, in pixels
*/
public static final int MARGIN_SIZE = 32;
/**
* Size in screen pixels in the IDE of the gutter shown for new rows and columns (in
* grid mode)
*/
private static final int NEW_CELL_WIDTH = 10;
/**
* Maximum size of a widget relative to a cell which is allowed to fit into a cell
* (and thereby enlarge it) before it is spread with row or column spans.
*/
public static final double MAX_CELL_DIFFERENCE = 1.2;
/** Whether debugging diagnostics is available in the toolbar */
private static final boolean CAN_DEBUG =
VALUE_TRUE.equals(System.getenv("ADT_DEBUG_GRIDLAYOUT")); //$NON-NLS-1$
private static final String ACTION_ADD_ROW = "_addrow"; //$NON-NLS-1$
private static final String ACTION_REMOVE_ROW = "_removerow"; //$NON-NLS-1$
private static final String ACTION_ADD_COL = "_addcol"; //$NON-NLS-1$
private static final String ACTION_REMOVE_COL = "_removecol"; //$NON-NLS-1$
private static final String ACTION_ORIENTATION = "_orientation"; //$NON-NLS-1$
private static final String ACTION_SHOW_STRUCTURE = "_structure"; //$NON-NLS-1$
private static final String ACTION_GRID_MODE = "_gridmode"; //$NON-NLS-1$
private static final String ACTION_SNAP = "_snap"; //$NON-NLS-1$
private static final String ACTION_DEBUG = "_debug"; //$NON-NLS-1$
private static final URL ICON_HORIZONTAL = GridLayoutRule.class.getResource("hlinear.png"); //$NON-NLS-1$
private static final URL ICON_VERTICAL = GridLayoutRule.class.getResource("vlinear.png"); //$NON-NLS-1$
private static final URL ICON_ADD_ROW = GridLayoutRule.class.getResource("addrow.png"); //$NON-NLS-1$
private static final URL ICON_REMOVE_ROW = GridLayoutRule.class.getResource("removerow.png"); //$NON-NLS-1$
private static final URL ICON_ADD_COL = GridLayoutRule.class.getResource("addcol.png"); //$NON-NLS-1$
private static final URL ICON_REMOVE_COL = GridLayoutRule.class.getResource("removecol.png"); //$NON-NLS-1$
private static final URL ICON_SHOW_STRUCT = GridLayoutRule.class.getResource("showgrid.png"); //$NON-NLS-1$
private static final URL ICON_GRID_MODE = GridLayoutRule.class.getResource("gridmode.png"); //$NON-NLS-1$
private static final URL ICON_SNAP = GridLayoutRule.class.getResource("snap.png"); //$NON-NLS-1$
/**
* Whether the IDE should show diagnostics for debugging the grid layout - including
* spacers visibly in the outline, showing row and column numbers, and so on
*/
public static boolean sDebugGridLayout = CAN_DEBUG;
/** Whether the structure (grid model) should be displayed persistently to the user */
public static boolean sShowStructure = false;
/** Whether the drop positions should snap to a regular grid */
public static boolean sSnapToGrid = false;
/**
* Whether the grid is edited in "grid mode" where the operations are row/column based
* rather than free-form
*/
public static boolean sGridMode = true;
/** Constructs a new {@link GridLayoutRule} */
public GridLayoutRule() {
}
@Override
public void addLayoutActions(
@NonNull List<RuleAction> actions,
final @NonNull INode parentNode,
final @NonNull List<? extends INode> children) {
super.addLayoutActions(actions, parentNode, children);
String namespace = getNamespace(parentNode);
Choices orientationAction = RuleAction.createChoices(
ACTION_ORIENTATION,
"Orientation", //$NON-NLS-1$
new PropertyCallback(Collections.singletonList(parentNode),
"Change LinearLayout Orientation", namespace, ATTR_ORIENTATION), Arrays
.<String> asList("Set Horizontal Orientation", "Set Vertical Orientation"),
Arrays.<URL> asList(ICON_HORIZONTAL, ICON_VERTICAL), Arrays.<String> asList(
"horizontal", "vertical"), getCurrentOrientation(parentNode),
null /* icon */, -10, false);
orientationAction.setRadio(true);
actions.add(orientationAction);
// Gravity and margins
if (children != null && children.size() > 0) {
actions.add(RuleAction.createSeparator(35));
actions.add(createMarginAction(parentNode, children));
actions.add(createGravityAction(children, ATTR_LAYOUT_GRAVITY));
}
IMenuCallback actionCallback = new IMenuCallback() {
@Override
public void action(
final @NonNull RuleAction action,
@NonNull List<? extends INode> selectedNodes,
final @Nullable String valueId,
final @Nullable Boolean newValue) {
parentNode.editXml("Add/Remove Row/Column", new INodeHandler() {
@Override
public void handle(@NonNull INode n) {
String id = action.getId();
if (id.equals(ACTION_SHOW_STRUCTURE)) {
sShowStructure = !sShowStructure;
mRulesEngine.redraw();
return;
} else if (id.equals(ACTION_GRID_MODE)) {
sGridMode = !sGridMode;
mRulesEngine.redraw();
return;
} else if (id.equals(ACTION_SNAP)) {
sSnapToGrid = !sSnapToGrid;
mRulesEngine.redraw();
return;
} else if (id.equals(ACTION_DEBUG)) {
sDebugGridLayout = !sDebugGridLayout;
mRulesEngine.layout();
return;
}
GridModel grid = GridModel.get(mRulesEngine, parentNode, null);
if (id.equals(ACTION_ADD_ROW)) {
grid.addRow(children);
} else if (id.equals(ACTION_REMOVE_ROW)) {
grid.removeRows(children);
} else if (id.equals(ACTION_ADD_COL)) {
grid.addColumn(children);
} else if (id.equals(ACTION_REMOVE_COL)) {
grid.removeColumns(children);
}
}
});
}
};
actions.add(RuleAction.createSeparator(142));
actions.add(RuleAction.createToggle(ACTION_GRID_MODE, "Grid Model Mode",
sGridMode, actionCallback, ICON_GRID_MODE, 145, false));
// Add and Remove Column actions only apply in Grid Mode
if (sGridMode) {
actions.add(RuleAction.createToggle(ACTION_SHOW_STRUCTURE, "Show Structure",
sShowStructure, actionCallback, ICON_SHOW_STRUCT, 147, false));
// Add Row and Add Column
actions.add(RuleAction.createSeparator(150));
actions.add(RuleAction.createAction(ACTION_ADD_COL, "Add Column", actionCallback,
ICON_ADD_COL, 160, false /* supportsMultipleNodes */));
actions.add(RuleAction.createAction(ACTION_ADD_ROW, "Add Row", actionCallback,
ICON_ADD_ROW, 165, false));
// Remove Row and Remove Column (if something is selected)
if (children != null && children.size() > 0) {
// TODO: Add "Merge Columns" and "Merge Rows" ?
actions.add(RuleAction.createAction(ACTION_REMOVE_COL, "Remove Column",
actionCallback, ICON_REMOVE_COL, 170, false));
actions.add(RuleAction.createAction(ACTION_REMOVE_ROW, "Remove Row",
actionCallback, ICON_REMOVE_ROW, 175, false));
}
actions.add(RuleAction.createSeparator(185));
} else {
actions.add(RuleAction.createToggle(ACTION_SHOW_STRUCTURE, "Show Structure",
sShowStructure, actionCallback, ICON_SHOW_STRUCT, 190, false));
// Snap to Grid and Show Structure are only relevant in free form mode
actions.add(RuleAction.createToggle(ACTION_SNAP, "Snap to Grid",
sSnapToGrid, actionCallback, ICON_SNAP, 200, false));
}
// Temporary: Diagnostics for GridLayout
if (CAN_DEBUG) {
actions.add(RuleAction.createToggle(ACTION_DEBUG, "Debug",
sDebugGridLayout, actionCallback, null, 210, false));
}
}
/**
* Returns the orientation attribute value currently used by the node (even if not
* defined, in which case the default horizontal value is returned)
*/
private String getCurrentOrientation(final INode node) {
String orientation = node.getStringAttr(getNamespace(node), ATTR_ORIENTATION);
if (orientation == null || orientation.length() == 0) {
orientation = VALUE_HORIZONTAL;
}
return orientation;
}
@Override
public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView,
@Nullable IDragElement[] elements) {
GridDropHandler userData = new GridDropHandler(this, targetNode, targetView);
IFeedbackPainter painter = GridLayoutPainter.createDropFeedbackPainter(this, elements);
return new DropFeedback(userData, painter);
}
@Override
public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements,
@Nullable DropFeedback feedback, @NonNull Point p) {
if (feedback == null) {
return null;
}
feedback.requestPaint = true;
GridDropHandler handler = (GridDropHandler) feedback.userData;
handler.computeMatches(feedback, p);
return feedback;
}
@Override
public void onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements,
@Nullable DropFeedback feedback, @NonNull Point p) {
if (feedback == null) {
return;
}
Rect b = targetNode.getBounds();
if (!b.isValid()) {
return;
}
GridDropHandler dropHandler = (GridDropHandler) feedback.userData;
if (dropHandler.getRowMatch() == null || dropHandler.getColumnMatch() == null) {
return;
}
// Collect IDs from dropped elements and remap them to new IDs
// if this is a copy or from a different canvas.
Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements,
feedback.isCopy || !feedback.sameCanvas);
for (IDragElement element : elements) {
INode newChild;
if (!sGridMode) {
newChild = dropHandler.handleFreeFormDrop(targetNode, element);
} else {
newChild = dropHandler.handleGridModeDrop(targetNode, element);
}
// Copy all the attributes, modifying them as needed.
addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER);
addInnerElements(newChild, element, idMap);
}
}
@Override
public void onChildInserted(@NonNull INode node, @NonNull INode parent,
@NonNull InsertType insertType) {
if (insertType == InsertType.MOVE_WITHIN) {
// Don't adjust widths/heights/weights when just moving within a single layout
return;
}
if (GridModel.isSpace(node.getFqcn())) {
return;
}
// Attempt to set "fill" properties on newly added views such that for example
// a text field will stretch horizontally.
String fqcn = node.getFqcn();
IViewMetadata metadata = mRulesEngine.getMetadata(fqcn);
FillPreference fill = metadata.getFillPreference();
String gravity = computeDefaultGravity(fill);
if (gravity != null) {
node.setAttribute(getNamespace(parent), ATTR_LAYOUT_GRAVITY, gravity);
}
}
/**
* Returns the namespace URI to use for GridLayout-specific attributes, such
* as columnCount, layout_column, layout_column_span, layout_gravity etc.
*
* @param layout the GridLayout instance to look up the namespace for
* @return the namespace, never null
*/
public String getNamespace(INode layout) {
String namespace = ANDROID_URI;
String fqcn = layout.getFqcn();
if (!fqcn.equals(GRID_LAYOUT) && !fqcn.equals(FQCN_GRID_LAYOUT)) {
namespace = mRulesEngine.getAppNameSpace();
}
return namespace;
}
/**
* Computes the default gravity to be used for a widget of the given fill
* preference when added to a grid layout
*
* @param fill the fill preference for the widget
* @return the gravity value, or null, to be set on the widget
*/
public static String computeDefaultGravity(FillPreference fill) {
String horizontal = GRAVITY_VALUE_LEFT;
String vertical = null;
if (fill.fillHorizontally(true /*verticalContext*/)) {
horizontal = GRAVITY_VALUE_FILL_HORIZONTAL;
}
if (fill.fillVertically(true /*verticalContext*/)) {
vertical = GRAVITY_VALUE_FILL_VERTICAL;
}
String gravity;
if (horizontal == GRAVITY_VALUE_FILL_HORIZONTAL
&& vertical == GRAVITY_VALUE_FILL_VERTICAL) {
gravity = GRAVITY_VALUE_FILL;
} else if (vertical != null) {
gravity = horizontal + '|' + vertical;
} else {
gravity = horizontal;
}
return gravity;
}
@Override
public void onRemovingChildren(@NonNull List<INode> deleted, @NonNull INode parent,
boolean moved) {
super.onRemovingChildren(deleted, parent, moved);
if (!sGridMode) {
// Attempt to clean up spacer objects for any newly-empty rows or columns
// as the result of this deletion
GridModel grid = GridModel.get(mRulesEngine, parent, null);
grid.onDeleted(deleted);
}
}
@Override
protected void paintResizeFeedback(IGraphics gc, INode node, ResizeState state) {
if (!sGridMode) {
GridModel grid = getGrid(state);
GridLayoutPainter.paintResizeFeedback(gc, state.layout, grid);
}
if (resizingWidget(state)) {
super.paintResizeFeedback(gc, node, state);
} else {
GridModel grid = getGrid(state);
int startColumn = grid.getColumn(state.bounds.x);
int endColumn = grid.getColumn(state.bounds.x2());
int columnSpan = endColumn - startColumn + 1;
int startRow = grid.getRow(state.bounds.y);
int endRow = grid.getRow(state.bounds.y2());
int rowSpan = endRow - startRow + 1;
Rect cellBounds = grid.getCellBounds(startRow, startColumn, rowSpan, columnSpan);
gc.useStyle(DrawingStyle.RESIZE_PREVIEW);
gc.drawRect(cellBounds);
}
}
/** Returns the grid size cached on the given {@link ResizeState} object */
private GridModel getGrid(ResizeState resizeState) {
GridModel grid = (GridModel) resizeState.clientData;
if (grid == null) {
grid = GridModel.get(mRulesEngine, resizeState.layout, resizeState.layoutView);
resizeState.clientData = grid;
}
return grid;
}
@Override
protected void setNewSizeBounds(ResizeState state, INode node, INode layout,
Rect oldBounds, Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) {
if (resizingWidget(state)) {
if (state.fillWidth || state.fillHeight || state.wrapWidth || state.wrapHeight) {
GridModel grid = getGrid(state);
ViewData view = grid.getView(node);
if (view != null) {
String gravityString = grid.getGridAttribute(view.node, ATTR_LAYOUT_GRAVITY);
int gravity = GravityHelper.getGravity(gravityString, 0);
if (view.column > 0 && verticalEdge != null && state.fillWidth) {
state.fillWidth = false;
state.wrapWidth = true;
gravity &= ~GravityHelper.GRAVITY_HORIZ_MASK;
gravity |= GravityHelper.GRAVITY_FILL_HORIZ;
} else if (verticalEdge != null && state.wrapWidth) {
gravity &= ~GravityHelper.GRAVITY_HORIZ_MASK;
gravity |= GravityHelper.GRAVITY_LEFT;
}
if (view.row > 0 && horizontalEdge != null && state.fillHeight) {
state.fillHeight = false;
state.wrapHeight = true;
gravity &= ~GravityHelper.GRAVITY_VERT_MASK;
gravity |= GravityHelper.GRAVITY_FILL_VERT;
} else if (horizontalEdge != null && state.wrapHeight) {
gravity &= ~GravityHelper.GRAVITY_VERT_MASK;
gravity |= GravityHelper.GRAVITY_TOP;
}
gravityString = GravityHelper.getGravity(gravity);
grid.setGridAttribute(view.node, ATTR_LAYOUT_GRAVITY, gravityString);
// Fall through and set layout_width and/or layout_height to wrap_content
}
}
super.setNewSizeBounds(state, node, layout, oldBounds, newBounds, horizontalEdge,
verticalEdge);
} else {
Pair<Integer, Integer> spans = computeResizeSpans(state);
int rowSpan = spans.getFirst();
int columnSpan = spans.getSecond();
GridModel grid = getGrid(state);
grid.setColumnSpanAttribute(node, columnSpan);
grid.setRowSpanAttribute(node, rowSpan);
ViewData view = grid.getView(node);
if (view != null) {
String gravityString = grid.getGridAttribute(view.node, ATTR_LAYOUT_GRAVITY);
int gravity = GravityHelper.getGravity(gravityString, 0);
if (verticalEdge != null && columnSpan > 1) {
gravity &= ~GravityHelper.GRAVITY_HORIZ_MASK;
gravity |= GravityHelper.GRAVITY_FILL_HORIZ;
}
if (horizontalEdge != null && rowSpan > 1) {
gravity &= ~GravityHelper.GRAVITY_VERT_MASK;
gravity |= GravityHelper.GRAVITY_FILL_VERT;
}
gravityString = GravityHelper.getGravity(gravity);
grid.setGridAttribute(view.node, ATTR_LAYOUT_GRAVITY, gravityString);
}
}
}
@Override
protected String getResizeUpdateMessage(ResizeState state, INode child, INode parent,
Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) {
Pair<Integer, Integer> spans = computeResizeSpans(state);
if (resizingWidget(state)) {
String width = state.getWidthAttribute();
String height = state.getHeightAttribute();
String message;
if (horizontalEdge == null) {
message = width;
} else if (verticalEdge == null) {
message = height;
} else {
// U+00D7: Unicode for multiplication sign
message = String.format("%s \u00D7 %s", width, height);
}
// Tack on a tip about using the Shift modifier key
return String.format("%s\n(Press Shift to resize row/column spans)", message);
} else {
int rowSpan = spans.getFirst();
int columnSpan = spans.getSecond();
return String.format("ColumnSpan=%d, RowSpan=%d\n(Release Shift to resize widget itself)",
columnSpan, rowSpan);
}
}
/**
* Returns true if we're resizing the widget, and false if we're resizing the cell
* spans
*/
private static boolean resizingWidget(ResizeState state) {
return (state.modifierMask & DropFeedback.MODIFIER2) == 0;
}
/**
* Computes the new column and row spans as the result of the current resizing
* operation
*/
private Pair<Integer, Integer> computeResizeSpans(ResizeState state) {
GridModel grid = getGrid(state);
int startColumn = grid.getColumn(state.bounds.x);
int endColumn = grid.getColumn(state.bounds.x2());
int columnSpan = endColumn - startColumn + 1;
int startRow = grid.getRow(state.bounds.y);
int endRow = grid.getRow(state.bounds.y2());
int rowSpan = endRow - startRow + 1;
return Pair.of(rowSpan, columnSpan);
}
/**
* Returns the size of the new cell gutter in layout coordinates
*
* @return the size of the new cell gutter in layout coordinates
*/
public int getNewCellSize() {
return mRulesEngine.screenToLayout(NEW_CELL_WIDTH / 2);
}
@Override
public void paintSelectionFeedback(@NonNull IGraphics graphics, @NonNull INode parentNode,
@NonNull List<? extends INode> childNodes, @Nullable Object view) {
super.paintSelectionFeedback(graphics, parentNode, childNodes, view);
if (sShowStructure) {
// TODO: Cache the grid
if (view != null) {
if (GridLayoutPainter.paintStructure(view, DrawingStyle.GUIDELINE_DASHED,
parentNode, graphics)) {
return;
}
}
GridLayoutPainter.paintStructure(DrawingStyle.GUIDELINE_DASHED,
parentNode, graphics, GridModel.get(mRulesEngine, parentNode, view));
} else if (sDebugGridLayout) {
GridLayoutPainter.paintStructure(DrawingStyle.GRID,
parentNode, graphics, GridModel.get(mRulesEngine, parentNode, view));
}
// TBD: Highlight the cells around the selection, and display easy controls
// for for example tweaking the rowspan/colspan of a cell? (but only in grid mode)
}
/**
* Paste into a GridLayout. We have several possible behaviors (and many
* more than are listed here):
* <ol>
* <li> Preserve the current positions of the elements (if pasted from another
* canvas, not just XML markup copied from say a web site) and apply those
* into the current grid. This might mean "overwriting" (sitting on top of)
* existing elements.
* <li> Fill available "holes" in the grid.
* <li> Lay them out consecutively, row by row, like text.
* <li> Some hybrid approach, where I attempt to preserve the <b>relative</b>
* relationships (columns/wrapping, spacing between the pasted views etc)
* but I append them to the bottom of the layout on one or more new rows.
* <li> Try to paste at the current mouse position, if known, preserving the
* relative distances between the existing elements there.
* </ol>
* Attempting to preserve the current position isn't possible right now,
* because the clipboard data contains only the textual representation of
* the markup. (We'd need to stash position information from a previous
* layout render along with the clipboard data).
* <p>
* Currently, this implementation simply lays out the elements row by row,
* approach #3 above.
*/
@Override
public void onPaste(
@NonNull INode targetNode,
@Nullable Object targetView,
@NonNull IDragElement[] elements) {
DropFeedback feedback = onDropEnter(targetNode, targetView, elements);
if (feedback != null) {
Rect b = targetNode.getBounds();
if (!b.isValid()) {
return;
}
Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements,
true /* remap id's */);
for (IDragElement element : elements) {
// Skip <Space> elements and only insert the real elements being
// copied
if (elements.length > 1 && (FQCN_SPACE.equals(element.getFqcn())
|| FQCN_SPACE_V7.equals(element.getFqcn()))) {
continue;
}
String fqcn = element.getFqcn();
INode newChild = targetNode.appendChild(fqcn);
addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER);
// Ensure that we reset any potential row/column attributes from a different
// grid layout being copied from
GridDropHandler handler = (GridDropHandler) feedback.userData;
GridModel grid = handler.getGrid();
grid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, null);
grid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, null);
// TODO: Set columnSpans to avoid making these widgets completely
// break the layout
// Alternatively, I could just lay them all out on subsequent lines
// with a column span of columnSpan5
addInnerElements(newChild, element, idMap);
}
}
}
}