/* * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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.intellij.android.designer.model.layout; import com.android.SdkConstants; import com.android.tools.idea.designer.LinearLayoutResizeOperation; import com.android.tools.idea.designer.ResizeOperation; import com.intellij.android.designer.designSurface.TreeDropToOperation; import com.intellij.android.designer.designSurface.graphics.DirectionResizePoint; import com.intellij.android.designer.designSurface.graphics.DrawingStyle; import com.intellij.android.designer.designSurface.graphics.ResizeSelectionDecorator; import com.intellij.android.designer.designSurface.layout.LinearLayoutOperation; import com.intellij.android.designer.designSurface.layout.actions.LayoutMarginOperation; import com.intellij.android.designer.designSurface.layout.flow.FlowStaticDecorator; import com.intellij.android.designer.model.RadComponentOperations; import com.intellij.android.designer.model.RadViewComponent; import com.intellij.android.designer.model.RadViewLayoutWithData; import com.intellij.android.designer.model.layout.actions.*; import com.intellij.designer.componentTree.TreeEditOperation; import com.intellij.designer.designSurface.*; import com.intellij.designer.designSurface.selection.ResizePoint; import com.intellij.designer.model.RadComponent; import com.intellij.designer.utils.Position; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.Pair; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; import java.util.Arrays; import java.util.Iterator; import java.util.List; import static com.android.SdkConstants.*; import static com.intellij.android.designer.designSurface.graphics.DrawingStyle.SHOW_STATIC_GRID; /** * @author Alexander Lobas */ public class RadLinearLayout extends RadViewLayoutWithData implements ILayoutDecorator { private static final String[] LAYOUT_PARAMS = {"LinearLayout_Layout", "ViewGroup_MarginLayout"}; private ResizeSelectionDecorator mySelectionDecorator; private FlowStaticDecorator myLineDecorator; @Override @NotNull public String[] getLayoutParams() { return LAYOUT_PARAMS; } public boolean isHorizontal() { return !VALUE_VERTICAL.equals(((RadViewComponent)myContainer).getTag().getAttributeValue(ATTR_ORIENTATION, ANDROID_URI)); } @Override public EditOperation processChildOperation(OperationContext context) { if (context.isCreate() || context.isPaste() || context.isAdd() || context.isMove()) { if (context.isTree()) { if (TreeEditOperation.isTarget(myContainer, context)) { return new TreeDropToOperation(myContainer, context); } return null; } return new LinearLayoutOperation(myContainer, context, isHorizontal()); } if (context.is(ResizeOperation.TYPE)) { return new LinearLayoutResizeOperation(context); } if (context.is(LayoutMarginOperation.TYPE)) { return new LayoutMarginOperation(context); } return null; } private StaticDecorator getLineDecorator() { if (myLineDecorator == null) { myLineDecorator = new FlowStaticDecorator(myContainer) { @Override protected boolean isHorizontal() { return RadLinearLayout.this.isHorizontal(); } }; } return myLineDecorator; } @Override public void addStaticDecorators(List<StaticDecorator> decorators, List<RadComponent> selection) { //noinspection ConstantConditions if (!SHOW_STATIC_GRID) { return; } if (selection.contains(myContainer)) { if (!(myContainer.getParent().getLayout() instanceof ILayoutDecorator)) { decorators.add(getLineDecorator()); } } else { for (RadComponent component : selection) { if (component.getParent() == myContainer) { decorators.add(getLineDecorator()); return; } } super.addStaticDecorators(decorators, selection); } } private static final int POINTS_SIZE = 16; @Override public ComponentDecorator getChildSelectionDecorator(RadComponent component, List<RadComponent> selection) { if (mySelectionDecorator == null) { mySelectionDecorator = new ResizeSelectionDecorator(DrawingStyle.SELECTION) { @Override protected boolean visible(RadComponent component, ResizePoint point) { if (point.getType() == LayoutMarginOperation.TYPE) { boolean horizontal = isHorizontal(); Pair<Gravity, Gravity> gravity = Gravity.getSides(component); int direction = ((DirectionResizePoint)point).getDirection(); Rectangle bounds = component.getBounds(); boolean goodWidth = bounds.width >= POINTS_SIZE; boolean goodHeight = bounds.height >= POINTS_SIZE; if (direction == Position.WEST) { // left return (horizontal || gravity.first != Gravity.right) && goodHeight; } if (direction == Position.EAST) { // right return (horizontal || gravity.first != Gravity.left) && goodHeight; } if (direction == Position.NORTH) { // top return (!horizontal || gravity.second != Gravity.bottom) && goodWidth; } if (direction == Position.SOUTH) { // bottom return (!horizontal || gravity.second != Gravity.top) && goodWidth; } } return true; } }; } mySelectionDecorator.clear(); if (selection.size() == 1) { ResizeOperation.addResizePoints(mySelectionDecorator, (RadViewComponent)selection.get(0)); } else { ResizeOperation.addResizePoints(mySelectionDecorator); } return mySelectionDecorator; } ////////////////////////////////////////////////////////////////////////////////////////// // // Actions // ////////////////////////////////////////////////////////////////////////////////////////// /** * Returns true if this LinearLayout supports switching orientation. * * @return true if this layout supports orientations */ protected boolean supportsOrientation() { return true; } @Override public void addContainerSelectionActions(DesignerEditorPanel designer, DefaultActionGroup actionGroup, List<? extends RadViewComponent> selectedChildren) { RadViewComponent layout = (RadViewComponent)myContainer; List<? extends RadViewComponent> children = RadViewComponent.getViewComponents(layout.getChildren()); boolean addSeparator = false; if (supportsOrientation()) { actionGroup.add(new OrientationAction(designer, layout, true)); addSeparator = true; } if (isHorizontal()) { actionGroup.add(new BaselineAction(designer, layout)); addSeparator = true; } if (addSeparator) { actionGroup.addSeparator(); addSeparator = false; } if (!selectedChildren.isEmpty()) { actionGroup.add(new GravityAction(designer, selectedChildren)); addSeparator = true; } // TODO: Create margin action? if (addSeparator) { actionGroup.addSeparator(); } // Add in wrap width/height actions from the parent super.addContainerSelectionActions(designer, actionGroup, selectedChildren); actionGroup.addSeparator(); // For some actions (clear weights, distribute weights), if you've selected just one child, we want // the action to work as if it's operating on all the children. If however you select multiple children, // we're distributing the width among just those children. List<? extends RadViewComponent> multipleChildren = selectedChildren.size() <= 1 ? children : selectedChildren; actionGroup.add(new DistributeWeightsAction(designer, layout, multipleChildren)); if (!selectedChildren.isEmpty()) { actionGroup.add(new DominateWeightsAction(designer, layout, selectedChildren)); actionGroup.add(new AssignWeightAction(designer, layout, selectedChildren)); } actionGroup.add(new ClearWeightsAction(designer, layout, multipleChildren)); } private static final List<Gravity> HORIZONTALS = Arrays.asList(Gravity.left, Gravity.center, Gravity.right, null); private static final List<Gravity> VERTICALS = Arrays.asList(Gravity.top, Gravity.center, Gravity.bottom, null); private class GravityAction extends AbstractGravityAction<Gravity> { private Gravity mySelection; public GravityAction(DesignerEditorPanel designer, List<? extends RadViewComponent> components) { super(designer, components); } @NotNull @Override protected DefaultActionGroup createPopupActionGroup(JComponent button) { boolean horizontal = isHorizontal(); Gravity unknown = horizontal ? Gravity.left : Gravity.top; setItems(horizontal ? VERTICALS : HORIZONTALS, unknown); Iterator<? extends RadViewComponent> I = myComponents.iterator(); mySelection = LinearLayoutOperation.getGravity(horizontal, I.next()); while (I.hasNext()) { if (mySelection != LinearLayoutOperation.getGravity(horizontal, I.next())) { mySelection = unknown; break; } } return super.createPopupActionGroup(button); } @Override protected void update(Gravity item, Presentation presentation, boolean popup) { if (popup) { presentation.setIcon(mySelection == item ? CHECKED : null); presentation.setText(item == null ? "fill" : item.name()); } } @Override protected boolean selectionChanged(final Gravity item) { execute(new Runnable() { @Override public void run() { LinearLayoutOperation.applyGravity(isHorizontal(), item, myComponents); } }); return false; } @Override public void update() { } } @Override public boolean isWrapIn(List<RadComponent> components) { List<RadComponent> children = myContainer.getChildren(); int[] indexes = new int[components.size()]; for (int i = 0; i < indexes.length; i++) { indexes[i] = children.indexOf(components.get(i)); } Arrays.sort(indexes); for (int i = 0; i < indexes.length - 1; i++) { if (indexes[i + 1] - indexes[i] != 1) { return false; } } return true; } @Override public void wrapIn(final RadViewComponent newParent, final List<RadViewComponent> components) throws Exception { final boolean horizontal = isHorizontal(); RadViewComponent firstComponent = components.get(0); boolean single = components.size() == 1; String layoutWidth = single ? firstComponent.getTag().getAttributeValue("layout_width", SdkConstants.NS_RESOURCES) : "wrap_content"; String layoutHeight = single ? firstComponent.getTag().getAttributeValue("layout_height", SdkConstants.NS_RESOURCES) : "wrap_content"; String layoutGravity = firstComponent.getTag().getAttributeValue("layout_gravity", SdkConstants.NS_RESOURCES); if (horizontal) { for (RadViewComponent component : components.subList(1, components.size())) { String height = component.getTag().getAttributeValue("layout_height", SdkConstants.NS_RESOURCES); if ("fill_parent".equals(height) || "match_parent".equals(height)) { layoutHeight = "fill_parent"; layoutGravity = null; } if (layoutGravity != null && layoutGravity.equals(component.getTag().getAttributeValue("layout_gravity", SdkConstants.NS_RESOURCES))) { } } } else { } if (newParent.getLayout() instanceof RadLinearLayout) { RadLinearLayout layout = (RadLinearLayout)newParent.getLayout(); if (horizontal != layout.isHorizontal()) { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { newParent.getTag().setAttribute("orientation", SdkConstants.NS_RESOURCES, horizontal ? "horizontal" : "vertical"); } }); } } else { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { for (RadViewComponent component : components) { RadComponentOperations.deleteAttribute(component.getTag(), "layout_gravity"); } } }); } } }