/* * 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.navigation.*; import com.android.navigation.Dimension; import com.android.navigation.NavigationModel.Event; import com.android.tools.idea.editors.navigation.macros.Analyser; import com.android.tools.idea.editors.navigation.macros.FragmentEntry; 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.Point; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.Map; import static com.android.tools.idea.editors.navigation.NavigationView.Line; import static com.android.tools.idea.editors.navigation.Utilities.diff; class Selections { private static final Color SELECTION_COLOR = JBColor.BLUE; private static final int SELECTION_RECTANGLE_LINE_WIDTH = 4; 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 abstract void remove(); protected void configureInspector(Inspector inspector) { } } 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; } @Override protected void remove() { } } private static void configureHyperLinkLabelForClassName(final RenderingParameters renderingParameters, HyperlinkLabel link, 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 = Utilities.getPsiClass(renderingParameters.myConfiguration.getModule(), className); if (psiClass != null) { AndroidRootComponent.launchEditor(renderingParameters, psiClass.getContainingFile(), false); } } }); } private static void configureHyperlinkForXMLFile(final RenderingParameters renderingParameters, HyperlinkLabel link, @Nullable final String xmlFileName, final boolean isMenu) { link.setOpaque(false); link.setHyperlinkText(xmlFileName == null ? "" : xmlFileName); link.addHyperlinkListener(new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent) { PsiFile layoutXmlFile = NavigationView.getLayoutXmlFile(isMenu, xmlFileName, renderingParameters.myConfiguration, renderingParameters.myProject); AndroidRootComponent.launchEditor(renderingParameters, layoutXmlFile, false); } }); } 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 remove() { myNavigationModel.remove(myTransition); } @Override protected void configureInspector(Inspector inspector) { TransitionInspector transitionInspector = new TransitionInspector(); configureHyperLinkLabelForClassName(myRenderingParameters, transitionInspector.source, myTransition.getSource().getState().getClassName()); { JComboBox comboBox = transitionInspector.gesture; comboBox.addItem(Transition.PRESS); comboBox.addItem(Transition.SWIPE); 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 = myComponent.getLocation(); myState = state; myTransform = transform; } private void moveTo(Point location, boolean snap) { Point newLocation = Utilities.add(diff(location, myMouseDownLocation), myOrigComponentLocation); if (snap) { newLocation = Utilities.snap(newLocation, myTransform.modelToView(Dimension.create(NavigationView.MIDDLE_SNAP_GRID))); } myComponent.setLocation(newLocation); myNavigationModel.getStateToLocation().put(myState, myTransform.viewToModel(newLocation)); myNavigationModel.getListeners().notify(Event.update(Map.class)); // just avoid State.class, which would trigger reload } @Override protected void moveTo(Point location) { moveTo(location, false); } @Override protected void remove() { myNavigationModel.removeState(myState); } @Override protected Selection finaliseSelectionLocation(Point location) { moveTo(location, true); return this; } @Override protected void configureInspector(final Inspector inspector) { myState.accept(new State.Visitor<Void>() { @Override public Void visit(ActivityState state) { final Module module = myRenderingParameters.myConfiguration.getModule(); ActivityInspector activityInspector = new ActivityInspector(); { HyperlinkLabel link = activityInspector.classNameLabel; final String className = myState.getClassName(); configureHyperLinkLabelForClassName(myRenderingParameters, link, className); } { HyperlinkLabel link = activityInspector.xmlFileNameLabel; configureHyperlinkForXMLFile(myRenderingParameters, link, Analyser.getXMLFileName(module, myState.getClassName(), true), false); } { JPanel fragmentList = activityInspector.fragmentList; fragmentList.removeAll(); fragmentList.setLayout(new BoxLayout(fragmentList, BoxLayout.Y_AXIS)); for (FragmentEntry entry : state.getFragments()) { HyperlinkLabel hyperlinkLabel = new HyperlinkLabel(); configureHyperLinkLabelForClassName(myRenderingParameters, hyperlinkLabel, entry.className); fragmentList.add(hyperlinkLabel); } } { JPanel propertyList = activityInspector.propertyList; propertyList.removeAll(); propertyList.setLayout(new BoxLayout(propertyList, BoxLayout.Y_AXIS)); final String className = myState.getClassName(); PsiClass psiClass = Utilities.getPsiClass(module, className); for (String propertyName : Analyser.findProperties(psiClass)) { JLabel label = new JLabel(propertyName); label.setOpaque(false); propertyList.add(label); } } inspector.setInspectorComponent(activityInspector.container); return null; } @Override public Void visit(MenuState state) { MenuInspector menuInspector = new MenuInspector(); configureHyperlinkForXMLFile(myRenderingParameters, menuInspector.xmlFileNameLabel, myState.getXmlResourceName(), true); inspector.setInspectorComponent(menuInspector.container); return null; } }); } } static class RelationSelection extends Selection { private final AndroidRootComponent mySourceComponent; private final NavigationView myNavigationEditor; private final NavigationModel myNavigationModel; private final RenderedView myNamedLeaf; @NotNull private Point myMouseLocation; RelationSelection(NavigationModel navigationModel, @NotNull AndroidRootComponent sourceComponent, @NotNull Point mouseDownLocation, @Nullable RenderedView namedLeaf, @NotNull NavigationView navigationEditor) { myNavigationModel = navigationModel; mySourceComponent = sourceComponent; myMouseLocation = mouseDownLocation; myNamedLeaf = namedLeaf; myNavigationEditor = navigationEditor; } @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 = NavigationView.createLineGraphics(g, lineWidth); Rectangle sourceBounds = NavigationView.getBounds(mySourceComponent, myNamedLeaf); Rectangle destBounds = myNavigationEditor.getNamedLeafBoundsAt(mySourceComponent, myMouseLocation); Rectangle sourceComponentBounds = mySourceComponent.getBounds(); // if the mouse hasn't left the bounds of the originating component yet, use leaf bounds instead for the midLine calculation Rectangle startBounds = sourceComponentBounds.contains(myMouseLocation) ? sourceBounds : sourceComponentBounds; Line midLine = NavigationView.getMidLine(startBounds, new Rectangle(myMouseLocation)); Point[] controlPoints = NavigationView.getControlPoints(sourceBounds, destBounds, midLine); myNavigationEditor.drawTransition(lineGraphics, sourceBounds, destBounds, controlPoints); } @Override protected Selection finaliseSelectionLocation(Point mouseUpLocation) { Transition transition = myNavigationEditor.getTransition(mySourceComponent, myNamedLeaf, mouseUpLocation); if (transition != null) { myNavigationModel.add(transition); } return NULL; } @Override protected void remove() { } } }