blob: ac9177f677946500ef2de1860f7848ee96000ad1 [file] [log] [blame]
/*
* Copyright (C) 2014 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.rendering;
import com.android.ide.common.rendering.LayoutLibrary;
import com.android.ide.common.rendering.RenderSecurityManager;
import com.android.ide.common.rendering.api.ActionBarCallback;
import com.android.resources.ResourceFolderType;
import com.android.tools.idea.AndroidPsiUtils;
import com.android.tools.idea.configurations.RenderContext;
import com.android.tools.idea.model.ManifestInfo;
import com.android.tools.idea.model.ManifestInfo.ActivityAttributes;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiCompiledElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.xml.XmlFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.android.SdkConstants.*;
/**
* A callback to provide information related to the Action Bar as required by the
* {@link LayoutLibrary}
*/
public class ActionBarHandler extends ActionBarCallback {
private static final String ON_CREATE_OPTIONS_MENU = "onCreateOptionsMenu"; //$NON-NLS-1$
private static final Pattern MENU_FIELD_PATTERN = Pattern.compile("R\\.menu\\.([a-z0-9_]+)"); //$NON-NLS-1$
private final Object myCredential;
@NotNull
private RenderTask myRenderTask;
@Nullable
private List<String> myMenus;
ActionBarHandler(@NotNull RenderTask renderTask, @Nullable Object credential) {
myRenderTask = renderTask;
myCredential = credential;
}
@Override
public boolean getSplitActionBarWhenNarrow() {
ActivityAttributes attributes = getActivityAttributes();
if (attributes != null) {
return VALUE_SPLIT_ACTION_BAR_WHEN_NARROW.equals(attributes.getUiOptions());
}
return false;
}
// TODO: Handle this per file instead
/** Flag which controls whether we should be showing the menu */
private static boolean ourShowMenu = false;
public static boolean isShowingMenu(@SuppressWarnings("UnusedParameters") @Nullable RenderContext context) {
return ourShowMenu;
}
public static boolean showMenu(boolean showMenu, @Nullable RenderContext context, boolean repaint) {
if (showMenu != ourShowMenu) {
//noinspection AssignmentToStaticFieldFromInstanceMethod
ourShowMenu = showMenu;
if (context != null && repaint) {
context.requestRender();
}
return true;
}
return false;
}
@Override
public boolean isOverflowPopupNeeded() {
return ourShowMenu || ResourceHelper.getFolderType(myRenderTask.getPsiFile()) == ResourceFolderType.MENU;
}
@Override
public List<String> getMenuIdNames() {
if (myMenus != null) {
return myMenus;
}
boolean token = RenderSecurityManager.enterSafeRegion(myCredential);
try {
final XmlFile xmlFile = myRenderTask.getPsiFile();
String commaSeparatedMenus = xmlFile == null ? null : AndroidPsiUtils.getRootTagAttributeSafely(xmlFile, ATTR_MENU, TOOLS_URI);
if (commaSeparatedMenus != null) {
myMenus = new ArrayList<String>();
Iterables.addAll(myMenus, Splitter.on(',').trimResults().omitEmptyStrings().split(commaSeparatedMenus));
} else {
final String fqn = xmlFile == null ? null : AndroidPsiUtils.getDeclaredContextFqcn(myRenderTask.getModule(), xmlFile);
if (fqn != null) {
final Project project = xmlFile.getProject();
DumbService.getInstance(project).smartInvokeLater(new Runnable() {
@Override
public void run() {
// Glance at the onCreateOptionsMenu of the associated context and use any menus found there.
// This is just a simple textual search; we need to replace this with a proper model lookup.
PsiClass clz = JavaPsiFacade.getInstance(project).findClass(fqn, GlobalSearchScope.allScope(project));
if (clz != null) {
for (PsiMethod method : clz.findMethodsByName(ON_CREATE_OPTIONS_MENU, true)) {
if (method instanceof PsiCompiledElement) {
continue;
}
// TODO: This should instead try to use the GotoRelated implementation's notion
// of associated activities; see what is done in
// AndroidMissingOnClickHandlerInspection. However, the AndroidGotoRelatedProvider
// will first need to properly handle menus.
String matchText = method.getText();
Matcher matcher = MENU_FIELD_PATTERN.matcher(matchText);
Set<String> menus = Sets.newTreeSet();
int index = 0;
while (true) {
if (matcher.find(index)) {
menus.add(matcher.group(1));
index = matcher.end();
} else {
break;
}
}
if (!menus.isEmpty()) {
myMenus = new ArrayList<String>(menus);
}
}
}
}
});
}
}
if (myMenus == null) {
myMenus = Collections.emptyList();
}
} finally {
RenderSecurityManager.exitSafeRegion(token);
}
return myMenus;
}
@Override
public HomeButtonStyle getHomeButtonStyle() {
ActivityAttributes attributes = getActivityAttributes();
if (attributes != null && attributes.getParentActivity() != null) {
return HomeButtonStyle.SHOW_HOME_AS_UP;
}
return HomeButtonStyle.NONE;
}
@Override
public int getNavigationMode() {
XmlFile xmlFile = myRenderTask.getPsiFile();
String navMode = StringUtil.notNullize(xmlFile == null ? null : AndroidPsiUtils.getRootTagAttributeSafely(xmlFile, ATTR_NAV_MODE, TOOLS_URI)).trim();
if (navMode.equalsIgnoreCase(VALUE_NAV_MODE_TABS)) {
return NAVIGATION_MODE_TABS;
}
if (navMode.equalsIgnoreCase(VALUE_NAV_MODE_LIST)) {
return NAVIGATION_MODE_LIST;
}
return NAVIGATION_MODE_STANDARD;
}
/**
* If set to null, this searches for the associated menu using tools:context and tools:menu attributes.
* To set no menu, pass an empty list.
*/
public void setMenuIdNames(@Nullable List<String> menus) {
myMenus = menus;
}
@Nullable
private ActivityAttributes getActivityAttributes() {
boolean token = RenderSecurityManager.enterSafeRegion(myCredential);
try {
ManifestInfo manifest = ManifestInfo.get(myRenderTask.getModule(), false);
String activity = StringUtil.notNullize(myRenderTask.getConfiguration().getActivity());
return manifest.getActivityAttributes(activity);
} finally {
RenderSecurityManager.exitSafeRegion(token);
}
}
}