blob: f0e7e035678149527110037f2738ab77617c5a5d [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.relative;
import com.intellij.android.designer.AndroidDesignerUtils;
import com.intellij.android.designer.designSurface.AbstractEditOperation;
import com.intellij.android.designer.designSurface.feedbacks.TextFeedback;
import com.intellij.android.designer.model.RadViewComponent;
import com.intellij.designer.designSurface.FeedbackLayer;
import com.intellij.designer.designSurface.OperationContext;
import com.intellij.designer.model.RadComponent;
import com.intellij.openapi.application.ApplicationManager;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.android.SdkConstants.*;
public class RelativeLayoutDropOperation extends AbstractEditOperation {
private GuidelinePainter myFeedback;
private MoveHandler myMoveHandler;
private RadViewComponent myPrimary;
private MultiLineTooltipManager myTooltip;
public RelativeLayoutDropOperation(RadComponent container, OperationContext context) {
super(container, context);
}
@Override
public void showFeedback() {
FeedbackLayer layer = myContext.getArea().getFeedbackLayer();
List<RadViewComponent> viewComponents = RadViewComponent.getViewComponents(myComponents);
if (myFeedback == null) {
myMoveHandler = new MoveHandler((RadViewComponent)myContainer, viewComponents, myContext);
myFeedback = new GuidelinePainter(myMoveHandler);
layer.add(myFeedback);
myFeedback.setBounds(0, 0, layer.getWidth(), layer.getHeight());
myTooltip = new MultiLineTooltipManager(layer, 4);
// If multiple components are selected, designate the one under the mouse as the primary
myPrimary = viewComponents.get(0);
Point location = myContext.getLocation();
RadComponent pointedTo = myContext.getArea().findTarget(location.x, location.y, null);
if (pointedTo instanceof RadViewComponent && myComponents.contains(pointedTo)) {
myPrimary = (RadViewComponent)pointedTo;
}
}
Point moveDelta = myContext.getMoveDelta();
Rectangle newBounds = myPrimary.getBounds(layer);
if (newBounds.width == 0 || newBounds.height == 0) {
// Pasting, etc
Dimension size = AndroidDesignerUtils.computePreferredSize(myContext.getArea(), myPrimary, myContainer);
newBounds = new Rectangle(0, 0, size.width, size.height);
}
if (moveDelta != null) {
newBounds.x += moveDelta.x;
newBounds.y += moveDelta.y;
} else {
// Dropping from palette etc
Point location = myContext.getLocation();
newBounds.x = location.x - newBounds.width / 2;
newBounds.y = location.y - newBounds.height / 2;
}
myMoveHandler.updateMove(myPrimary, newBounds, myContext.getModifiers());
myFeedback.repaint();
// Update the text
describeMatch(myMoveHandler.getCurrentLeftMatch(), 0, myMoveHandler.getLeftMarginDp(), ATTR_LAYOUT_MARGIN_LEFT);
describeMatch(myMoveHandler.getCurrentRightMatch(), 1, myMoveHandler.getRightMarginDp(), ATTR_LAYOUT_MARGIN_RIGHT);
describeMatch(myMoveHandler.getCurrentTopMatch(), 2, myMoveHandler.getTopMarginDp(), ATTR_LAYOUT_MARGIN_TOP);
describeMatch(myMoveHandler.getCurrentBottomMatch(), 3, myMoveHandler.getBottomMarginDp(), ATTR_LAYOUT_MARGIN_BOTTOM);
// Position the tooltip
Point location = myContext.getLocation();
myTooltip.update((RadViewComponent) myContainer, location);
}
private void describeMatch(Match m, int line, int margin, String marginAttribute) {
if (m == null) {
myTooltip.setVisible(line, false);
return;
}
myTooltip.setVisible(line, true);
TextFeedback feedback = myTooltip.getFeedback(line);
m.describe(feedback, margin, marginAttribute);
}
@Override
public void eraseFeedback() {
if (myFeedback != null) {
FeedbackLayer layer = myContext.getArea().getFeedbackLayer();
layer.remove(myFeedback);
myTooltip.dispose();
myFeedback = null;
myTooltip = null;
layer.repaint();
}
}
@Override
public void execute() throws Exception {
if (myContext.isCreate() || myContext.isPaste() || myContext.isAdd()) {
super.execute();
// TODO: Return here?
}
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
myMoveHandler.removeCycles();
RadViewComponent previous = null;
for (RadViewComponent component : RadViewComponent.getViewComponents(myComponents)) {
if (previous == null) {
myMoveHandler.applyConstraints(component);
} else {
// Arrange the nodes next to each other, depending on which
// edge we are attaching to. For example, if attaching to the
// top edge, arrange the subsequent nodes in a column below it.
//
// TODO: Try to do something smarter here where we detect
// constraints between the dragged edges, and we preserve these.
// We have to do this carefully though because if the
// constraints go through some other nodes not part of the
// selection, this doesn't work right, and you might be
// dragging several connected components, which we'd then
// need to stitch together such that they are all visible.
myMoveHandler.attachPrevious(previous, component);
}
previous = component;
}
}
});
}
/**
* Returns a list of valid constraint targets for the given source component. This
* checks for cycles and ensures that only components that will not introduce a
* cycle are selected (which is used to determine whether we are looking for
* a horizontal cycle or a vertical cycle or in the case of center, both)
*
* @param attributeName the relative layout attribute
* @param layout the relative layout
* @param sources the sources component
* @return a list of components you can attach to
*/
@NotNull
public static List<RadComponent> getValidTargets(String attributeName, RadViewComponent layout, List<RadViewComponent> sources) {
List<RadComponent> result = new ArrayList<RadComponent>();
ConstraintType constraintType = ConstraintType.fromAttribute(attributeName);
assert constraintType != null : attributeName;
DependencyGraph graph = DependencyGraph.get(layout);
Set<RadViewComponent> horizontalDependencies = constraintType.horizontalEdge ? graph.dependsOn(sources, false /* verticalEdge */)
: Collections.<RadViewComponent>emptySet();
Set<RadViewComponent> verticalDependencies = constraintType.verticalEdge ? graph.dependsOn(sources, true /* verticalEdge */)
: Collections.<RadViewComponent>emptySet();
for (RadViewComponent component : RadViewComponent.getViewComponents(layout.getChildren())) {
if (sources.contains(component)) {
continue;
}
if (horizontalDependencies.contains(component) || verticalDependencies.contains(component)) {
continue;
}
result.add(component);
}
return result;
}
}