blob: 418718a3d7d50c7313ba0339a39cf7d0f4fbf0de [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.android.tools.idea.editors.navigation;
import com.android.tools.idea.editors.navigation.macros.Analyser;
import com.android.tools.idea.editors.navigation.macros.FragmentEntry;
import com.android.tools.idea.editors.navigation.model.*;
import com.android.tools.idea.rendering.RenderedView;
import com.intellij.openapi.module.Module;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.JBColor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Map;
import static com.android.tools.idea.editors.navigation.NavigationEditorUtils.Line;
import static com.android.tools.idea.editors.navigation.NavigationEditorUtils.diff;
import static com.android.tools.idea.editors.navigation.NavigationEditorUtils.sum;
class Selections {
private static final Color SELECTION_COLOR = JBColor.BLUE;
private static final int SELECTION_RECTANGLE_LINE_WIDTH = 2;
public static final Event STATE_LOCATIONS_UPDATED = Event.update(Map.class); // just avoid State.class, which would trigger reload
public static final boolean MOVE_MENUS_WITH_ACTIVITIES = true;
public static final boolean SNAP_ON_MOUSE_UP = false;
public static Selection NULL = new EmptySelection();
abstract static class Selection {
protected abstract void moveTo(Point location);
protected abstract Selection finaliseSelectionLocation(Point location);
protected abstract void paint(Graphics g, boolean hasFocus);
protected abstract void paintOver(Graphics g);
protected void configureInspector(Inspector inspector) { }
protected void onSetup() { }
protected void onRemove() { }
}
private static class EmptySelection extends Selection {
@Override
protected void moveTo(Point location) {
}
@Override
protected void paint(Graphics g, boolean hasFocus) {
}
@Override
protected void paintOver(Graphics g) {
}
@Override
protected Selection finaliseSelectionLocation(Point location) {
return this;
}
}
private static void configureHyperLinkLabelForClassName(final RenderingParameters renderingParameters,
HyperlinkLabel link,
@Nullable final String className) {
link.setOpaque(false);
if (className == null) {
return;
}
link.setHyperlinkText(className.substring(1 + className.lastIndexOf('.')));
link.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent) {
PsiClass psiClass = NavigationEditorUtils.getPsiClass(renderingParameters.configuration.getModule(), className);
if (psiClass != null) {
AndroidRootComponent.launchEditor(renderingParameters, psiClass.getContainingFile(), false);
}
}
});
}
private static void configureHyperlinkForXMLFile(final RenderingParameters renderingParameters,
HyperlinkLabel link,
@Nullable final String linkText,
@Nullable final String xmlFileName,
final boolean isMenu) {
link.setOpaque(false);
link.setHyperlinkText(linkText == null ? "" : linkText);
link.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent) {
PsiFile layoutXmlFile =
NavigationView.getLayoutXmlFile(isMenu, xmlFileName, renderingParameters.configuration, renderingParameters.project);
AndroidRootComponent.launchEditor(renderingParameters, layoutXmlFile, false);
}
});
}
private static void configureHyperlinkForXMLFile(final RenderingParameters renderingParameters,
HyperlinkLabel link,
@Nullable final String xmlFileName,
final boolean isMenu) {
configureHyperlinkForXMLFile(renderingParameters, link, xmlFileName, xmlFileName, isMenu);
}
static class ComponentSelection<T extends Component> extends Selection {
protected final RenderingParameters myRenderingParameters;
protected final T myComponent;
protected final Transition myTransition;
protected final NavigationModel myNavigationModel;
ComponentSelection(RenderingParameters renderingParameters, NavigationModel navigationModel, T component, Transition transition) {
myRenderingParameters = renderingParameters;
myNavigationModel = navigationModel;
myComponent = component;
myTransition = transition;
}
@Override
protected void moveTo(Point location) {
}
@Override
protected void paint(Graphics g, boolean hasFocus) {
if (hasFocus) {
Graphics2D g2D = (Graphics2D)g.create();
g2D.setStroke(new BasicStroke(SELECTION_RECTANGLE_LINE_WIDTH));
g2D.setColor(SELECTION_COLOR);
Rectangle selection = myComponent.getBounds();
int l = SELECTION_RECTANGLE_LINE_WIDTH / 2;
selection.grow(l, l);
g2D.drawRect(selection.x, selection.y, selection.width, selection.height);
}
}
@Override
protected void paintOver(Graphics g) {
}
@Override
protected Selection finaliseSelectionLocation(Point location) {
return this;
}
@Override
protected void configureInspector(Inspector inspector) {
final Module module = myRenderingParameters.configuration.getModule();
final TransitionInspector transitionInspector = new TransitionInspector();
final Locator source = myTransition.getSource();
State sourceState = source.getState();
final boolean isFragment = source.getFragmentClassName() != null;
final String hostClassName = isFragment ? source.getFragmentClassName() : sourceState.getClassName();
configureHyperLinkLabelForClassName(myRenderingParameters, transitionInspector.source, hostClassName);
sourceState.accept(new State.Visitor() {
@Override
public void visit(ActivityState state) {
String xmlFileName = Analyser.getXMLFileName(module, hostClassName, !isFragment);
configureHyperlinkForXMLFile(myRenderingParameters, transitionInspector.sourceViewId, source.getViewId(), xmlFileName, false);
}
@Override
public void visit(MenuState state) {
String xmlFileName = state.getXmlResourceName();
configureHyperlinkForXMLFile(myRenderingParameters, transitionInspector.sourceViewId, source.getViewId(), xmlFileName, true);
}
});
JComboBox comboBox = transitionInspector.gesture;
comboBox.addItem(Transition.PRESS);
comboBox.setSelectedItem(myTransition.getType());
comboBox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent itemEvent) {
myTransition.setType((String)itemEvent.getItem());
myNavigationModel.getListeners().notify(Event.update(Transition.class));
}
});
configureHyperLinkLabelForClassName(myRenderingParameters, transitionInspector.destination,
myTransition.getDestination().getState().getClassName());
inspector.setInspectorComponent(transitionInspector.container);
}
}
static class AndroidRootComponentSelection extends ComponentSelection<AndroidRootComponent> {
protected final Point myMouseDownLocation;
protected final Point myOrigComponentLocation;
@NotNull private final State myState;
private final Transform myTransform;
AndroidRootComponentSelection(NavigationModel navigationModel,
AndroidRootComponent component,
Transition transition,
RenderingParameters renderingParameters,
Point mouseDownLocation,
@NotNull State state,
Transform transform) {
super(renderingParameters, navigationModel, component, transition);
myMouseDownLocation = mouseDownLocation;
myOrigComponentLocation = AndroidRootComponent.relativePoint(myComponent.getLocation());
myState = state;
myTransform = transform;
}
private void moveTo(Point location, boolean snap) {
Point newLocation = sum(diff(location, myMouseDownLocation), myOrigComponentLocation);
if (snap) {
newLocation = NavigationEditorUtils
.snap(newLocation, myTransform.modelToView(ModelDimension.create(NavigationView.MIDDLE_SNAP_GRID)));
}
Map<State, ModelPoint> stateToLocation = myNavigationModel.getStateToLocation();
Point oldLocation = myTransform.modelToView(stateToLocation.get(myState));
stateToLocation.put(myState, myTransform.viewToModel(newLocation));
//noinspection ConstantConditions
if (MOVE_MENUS_WITH_ACTIVITIES && myState instanceof ActivityState) {
MenuState menuState = myNavigationModel.findAssociatedMenuState((ActivityState)myState);
if (menuState != null) {
Point delta = diff(newLocation, oldLocation);
stateToLocation.put(menuState, myTransform.viewToModel(sum(delta, myTransform.modelToView(stateToLocation.get(menuState)))));
}
}
myNavigationModel.getListeners().notify(STATE_LOCATIONS_UPDATED);
}
@Override
protected void moveTo(Point location) {
moveTo(location, false);
}
@Override
protected Selection finaliseSelectionLocation(Point location) {
moveTo(location, SNAP_ON_MOUSE_UP);
return this;
}
@Override
protected void paint(Graphics g, boolean hasFocus) {
}
@Override
protected void onSetup() {
myComponent.setSelected(true);
}
@Override
protected void onRemove() {
myComponent.setSelected(false);
}
@Override
protected void configureInspector(final Inspector inspector) {
myState.accept(new State.Visitor() {
@Override
public void visit(ActivityState activity) {
final Module module = myRenderingParameters.configuration.getModule();
ActivityInspector activityInspector = new ActivityInspector();
HyperlinkLabel classNameLink = activityInspector.classNameLabel;
final String className = activity.getClassName();
configureHyperLinkLabelForClassName(myRenderingParameters, classNameLink, className);
HyperlinkLabel xmlFileLink = activityInspector.xmlFileNameLabel;
String xmlFileName = Analyser.getXMLFileName(module, activity.getClassName(), true);
configureHyperlinkForXMLFile(myRenderingParameters, xmlFileLink, xmlFileName, false);
JPanel fragmentList = activityInspector.fragmentList;
fragmentList.removeAll();
fragmentList.setLayout(new BoxLayout(fragmentList, BoxLayout.Y_AXIS));
for (FragmentEntry entry : activity.getFragments()) {
HyperlinkLabel hyperlinkLabel = new HyperlinkLabel();
configureHyperLinkLabelForClassName(myRenderingParameters, hyperlinkLabel, entry.className);
fragmentList.add(hyperlinkLabel);
}
inspector.setInspectorComponent(activityInspector.container);
}
@Override
public void visit(MenuState menu) {
MenuInspector menuInspector = new MenuInspector();
configureHyperLinkLabelForClassName(myRenderingParameters, menuInspector.classNameLabel, menu.getClassName());
configureHyperlinkForXMLFile(myRenderingParameters, menuInspector.xmlFileNameLabel, menu.getXmlResourceName(), true);
inspector.setInspectorComponent(menuInspector.container);
}
});
}
}
static class ViewSelection extends Selection {
private final AndroidRootComponent mySourceComponent;
private final NavigationView myNavigationView;
private final RenderedView myNamedLeaf;
@NotNull private Point myMouseLocation;
ViewSelection(@NotNull AndroidRootComponent sourceComponent,
@NotNull Point mouseDownLocation,
@Nullable RenderedView namedLeaf,
@NotNull NavigationView navigationView) {
mySourceComponent = sourceComponent;
myMouseLocation = mouseDownLocation;
myNamedLeaf = namedLeaf;
myNavigationView = navigationView;
}
@Override
protected void moveTo(Point location) {
myMouseLocation = location;
}
@Override
protected void paint(Graphics g, boolean hasFocus) {
}
@Override
protected void paintOver(Graphics g) {
int lineWidth = mySourceComponent.transform.modelToViewW(NavigationView.LINE_WIDTH);
Graphics2D lineGraphics = NavigationEditorUtils.createLineGraphics(g, lineWidth);
Rectangle sourceBounds = NavigationView.getBounds(mySourceComponent, myNamedLeaf);
Rectangle destBounds = myNavigationView.getNamedLeafBoundsAt(mySourceComponent, myMouseLocation, false);
Line midLine = NavigationEditorUtils.getMidLine(sourceBounds, new Rectangle(myMouseLocation));
Point[] controlPoints = NavigationView.getControlPoints(sourceBounds, destBounds, midLine);
myNavigationView.drawTransition(lineGraphics, sourceBounds, destBounds, controlPoints);
}
@Override
protected Selection finaliseSelectionLocation(Point mouseUpLocation) {
myNavigationView.createTransition(mySourceComponent, myNamedLeaf, mouseUpLocation);
return NULL;
}
}
}