blob: 74b871d387ff4a9b2fc9d08a5269778b70e5a763 [file] [log] [blame]
/*
* 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 = "match_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");
}
}
});
}
}
}