blob: 63a7f9727257801d65d9fbfd53cb7d08efb6d394 [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.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.ui.JBColor;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.util.*;
import java.util.List;
public class NavigationEditorUtils {
public static final Dimension ZERO_SIZE = new Dimension(0, 0);
static final Color TRANSITION_LINE_COLOR = new JBColor(new Color(80, 80, 255), new Color(40, 40, 255));
public static Point sum(Point p1, Point p2) {
return new Point(p1.x + p2.x, p1.y + p2.y);
}
public static Point diff(Point p1, Point p2) {
return new Point(p1.x - p2.x, p1.y - p2.y);
}
public static double length(Point p) {
return Math.sqrt(p.x * p.x + p.y * p.y);
}
public static Point scale(Point p, float k) {
return new Point((int)(k * p.x), (int)(k * p.y));
}
public static Dimension scale(Dimension d, float k) {
return new Dimension((int)(k * d.width), (int)(k * d.height));
}
private static int snap(int i, int d) {
return ((int)Math.round((double)i / d)) * d;
}
public static Point snap(Point p, Dimension gridSize) {
return new Point(snap(p.x, gridSize.width), snap(p.y, gridSize.height));
}
public static Point midPoint(Point p1, Point p2) {
return scale(sum(p1, p2), 0.5f);
}
public static Point midPoint(Dimension size) {
return point(scale(size, 0.5f));
}
public static Point point(Dimension d) {
return new Point(d.width, d.height);
}
public static Point project(Point p, Rectangle r) {
return new Point(clamp(p.x, x1(r), x2(r)), clamp(p.y, y1(r), y2(r)));
}
public static Point centre(@NotNull Rectangle r) {
return new Point(r.x + r.width / 2, r.y + r.height / 2);
}
/**
* Translates a Java file name to a XML file name according
* to Android naming convention.
* <p/>
* Doesn't append .xml extension
*
* @return XML file name associated with Java file name
*/
public static String getXmlFileNameFromJavaFileName(String javaFileName) {
if (javaFileName.endsWith(".java")) {
// cut off ".java"
javaFileName = javaFileName.substring(0, javaFileName.length() - 5);
}
char[] charsJava = javaFileName.toCharArray();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < charsJava.length; i++) {
char currentChar = charsJava[i];
if (Character.isUpperCase(currentChar) && i != 0) {
stringBuilder.append('_');
}
stringBuilder.append(Character.toLowerCase(currentChar));
}
return stringBuilder.toString();
}
/**
* Translates a XML file name to a Java file name according
* to Android naming convention.
* <p/>
* Doesn't append .java extension
*
* @return Java file name associated with XML file name
*/
@SuppressWarnings("AssignmentToForLoopParameter")
public static String getJavaFileNameFromXmlFileName(String xmlFileName) {
if (xmlFileName.endsWith(".xml")) {
// cut off ".xm"
xmlFileName = xmlFileName.substring(0, xmlFileName.length() - 4);
}
char[] charsXml = xmlFileName.toCharArray();
StringBuilder stringBuilder = new StringBuilder();
// make the first char upper case
stringBuilder.append(Character.toUpperCase(charsXml[0]));
// start looking for '_' at the second char
for (int i = 1; i < charsXml.length; i++) {
char currentChar = charsXml[i];
if (currentChar == '_') {
// skip '_' and add the next char as upper case
char toAppend = Character.toUpperCase(charsXml[++i]);
stringBuilder.append(toAppend);
}
else {
stringBuilder.append(currentChar);
}
}
return stringBuilder.toString();
}
static void drawArrow(Graphics g1, int x1, int y1, int x2, int y2, int lineWidth) {
// x1 and y1 are coordinates of circle or rectangle
// x2 and y2 are coordinates of circle or rectangle, to this point is directed the arrow
Graphics2D g = (Graphics2D)g1.create();
double dx = x2 - x1;
double dy = y2 - y1;
double angle = Math.atan2(dy, dx);
int len = (int)Math.sqrt(dx * dx + dy * dy);
AffineTransform t = AffineTransform.getTranslateInstance(x1, y1);
t.concatenate(AffineTransform.getRotateInstance(angle));
g.transform(t);
g.drawLine(0, 0, len, 0);
Dimension arrowHeadSize = new Dimension(lineWidth * 6, lineWidth * 3);
int basePosition = len - arrowHeadSize.width;
int height = arrowHeadSize.height;
g.fillPolygon(new int[]{len, basePosition, basePosition, len}, new int[]{0, -height, height, 0}, 4);
}
static <T> Condition<T> instanceOf(final Class<?> type) {
return new Condition<T>() {
@Override
public boolean value(Object o) {
return type.isAssignableFrom(o.getClass());
}
};
}
static int sign(int x) {
return x > 0 ? 1 : x < 0 ? -1 : 0;
}
static Dimension notNull(@Nullable Dimension d) {
return d == null ? ZERO_SIZE : d;
}
@Nullable
public static PsiClass getPsiClass(Module module, String className) {
Project project = module.getProject();
JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
GlobalSearchScope scope = module.getModuleWithLibrariesScope();
return facade.findClass(className, scope);
}
@Nullable
public static PsiMethod findMethodBySignature(@NotNull PsiClass psiClass, @NotNull String signature) {
PsiMethod template = createMethodFromText(psiClass, signature);
return psiClass.findMethodBySignature(template, false);
}
public static PsiMethod createMethodFromText(PsiClass psiClass, String text) {
return createMethodFromText(psiClass.getProject(), text, psiClass);
}
public static PsiMethod createMethodFromText(Project project, String text, @Nullable PsiElement context) {
return JavaPsiFacade.getInstance(project).getElementFactory().createMethodFromText(text, context);
}
public static VirtualFile mkDirs(VirtualFile dir, String path) throws IOException {
for(String dirName : path.split("/")) {
VirtualFile existingDir = dir.findFileByRelativePath(dirName);
//noinspection ConstantConditions
dir = (existingDir != null) ? existingDir : dir.createChildDirectory(null, dirName);
}
return dir;
}
public static VirtualFile getNavigationFile(final VirtualFile baseDir, String moduleName, String deviceQualifier, final String fileName) {
final String relativePathOfNavDir = NavigationEditor.NAVIGATION_DIRECTORY + "/" + moduleName + "/" + deviceQualifier;
VirtualFile navFile = baseDir.findFileByRelativePath(relativePathOfNavDir + "/" + fileName);
if (navFile == null) {
navFile = ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
@Override
public VirtualFile compute() {
try {
VirtualFile dir = mkDirs(baseDir, relativePathOfNavDir);
//noinspection ConstantConditions
return dir.createChildData(null, fileName);
}
catch (IOException e) {
assert false;
return null;
}
}
});
}
return navFile;
}
@Nullable
public static VirtualFile findLayoutFile(List<VirtualFile> resourceDirectories, String navigationDirectoryName) {
String qualifier = removePrefixIfPresent(NavigationEditor.DEFAULT_RESOURCE_FOLDER, navigationDirectoryName);
String layoutDirName = NavigationEditor.LAYOUT_DIR_NAME + qualifier;
for (VirtualFile root : resourceDirectories) {
for (VirtualFile dir : root.getChildren()) {
if (dir.isDirectory() && dir.getName().equals(layoutDirName)) {
for (VirtualFile file : dir.getChildren()) {
String fileName = file.getName();
if (!fileName.startsWith(".") && fileName.endsWith(".xml")) { // Ignore files like .DS_store on mac
return file;
}
}
}
}
}
return null;
}
private static String removePrefixIfPresent(String prefix, String s) {
return s.startsWith(prefix) ? s.substring(prefix.length()) : s;
}
@NotNull
public static Module[] getAndroidModules(Project project) {
if (project == null) {
return new Module[0];
}
Module[] modules = ModuleManager.getInstance(project).getModules();
List<Module> result = new ArrayList<Module>(modules.length);
for (Module module : modules) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null) {
List<VirtualFile> resourceDirectories = facet.getAllResourceDirectories();
VirtualFile layoutFile = findLayoutFile(resourceDirectories, NavigationEditor.DEFAULT_RESOURCE_FOLDER);
if (layoutFile != null) {
result.add(module);
}
}
}
return result.toArray(new Module[result.size()]);
}
@Nullable
public static Module findModule(@NotNull Module[] modules, @NotNull String name) {
for (Module module : modules) {
if (name.equals(module.getName())) {
return module;
}
}
return null;
}
public static Graphics2D createLineGraphics(Graphics g, int lineWidth) {
Graphics2D g2D = (Graphics2D)g.create();
g2D.setColor(TRANSITION_LINE_COLOR);
g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2D.setStroke(new BasicStroke(lineWidth));
return g2D;
}
static Rectangle getCorner(Point a, int cornerDiameter) {
int cornerRadius = cornerDiameter / 2;
return new Rectangle(a.x - cornerRadius, a.y - cornerRadius, cornerDiameter, cornerDiameter);
}
static void drawLine(Graphics g, Point a, Point b) {
g.drawLine(a.x, a.y, b.x, b.y);
}
static void drawArrow(Graphics g, Point a, Point b, int lineWidth) {
drawArrow(g, a.x, a.y, b.x, b.y, lineWidth);
}
static void drawRectangle(Graphics g, Rectangle r) {
g.drawRect(r.x, r.y, r.width, r.height);
}
private static int x1(Rectangle src) {
return src.x;
}
private static int x2(Rectangle dst) {
return dst.x + dst.width;
}
private static int y1(Rectangle src) {
return src.y;
}
private static int y2(Rectangle dst) {
return dst.y + dst.height;
}
private static int clamp(int i, int min, int max) {
return Math.max(min, Math.min(i, max));
}
private static boolean overlaps(int min1, int max1, int min2, int max2) {
return !(max1 < min2 || max2 < min1);
}
static Line getMidLine(Rectangle src, Rectangle dst) {
boolean xOverlap = overlaps(x1(src), x2(src), x1(dst), x2(dst));
boolean yOverlap = overlaps(y1(src), y2(src), y1(dst), y2(dst));
int dx = Math.min(Math.abs(x1(src) - x2(dst)), Math.abs(x1(dst) - x2(src)));
int dy = Math.min(Math.abs(y1(src) - y2(dst)), Math.abs(y1(dst) - y2(src)));
//noinspection SimplifiableConditionalExpression
boolean horizontal = xOverlap ? yOverlap ? dx >= dy : false : yOverlap ? true : dx >= dy;
int middle;
if (horizontal) {
middle = x1(src) - x2(dst) > 0 ? (x2(dst) + x1(src)) / 2 : (x2(src) + x1(dst)) / 2;
}
else {
middle = y1(src) - y2(dst) > 0 ? (y2(dst) + y1(src)) / 2 : (y2(src) + y1(dst)) / 2;
}
Point midSrc = centre(src);
Point a = horizontal ? new Point(middle, midSrc.y) : new Point(midSrc.x, middle);
Point b = horizontal ? new Point(middle, clamp(midSrc.y, y1(dst), y2(dst)))
: new Point(clamp(midSrc.x, x1(dst), x2(dst)), middle);
return new Line(a, b, horizontal);
}
static class Line {
public final Point a;
public final Point b;
public final boolean horizontal;
Line(Point a, Point b, boolean horizontal) {
this.a = a;
this.b = b;
this.horizontal = horizontal;
}
}
}