blob: e85da80b97ac5029e2064f44c6ae346de866d3b8 [file] [log] [blame]
/*
* Copyright 2016 The Bazel Authors. All rights reserved.
*
* 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.google.idea.blaze.android.run.binary;
import com.android.tools.idea.run.ConfigurationSpecificEditor;
import com.android.tools.idea.run.activity.ActivityLocatorUtils;
import com.android.tools.idea.run.util.LaunchUtils;
import com.google.idea.blaze.android.run.binary.instantrun.InstantRunExperiment;
import com.google.idea.blaze.base.ui.IntegerTextField;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.ide.util.TreeClassChooser;
import com.intellij.ide.util.TreeClassChooserFactory;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.fileTypes.PlainTextLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComponentWithBrowseButton;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Key;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.ui.EditorTextField;
import com.intellij.ui.LanguageTextField;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ResourceBundle;
/**
* The part of the Blaze Android run configuration editor that allows the user to pick an
* android_binary target and an activity to launch.
* Patterned after {@link org.jetbrains.android.run.ApplicationRunParameters}.
*/
class BlazeAndroidBinaryRunConfigurationStateEditor implements ConfigurationSpecificEditor<BlazeAndroidBinaryRunConfiguration> {
public static final Key<BlazeAndroidBinaryRunConfigurationStateEditor> ACTIVITY_CLASS_TEXT_FIELD_KEY =
Key.create("BlazeActivityClassTextField");
@NotNull
private final Project project;
@Nullable
private JPanel panel;
private ComponentWithBrowseButton<EditorTextField> activityField;
private JRadioButton launchNothingButton;
private JRadioButton launchDefaultButton;
private JRadioButton launchCustomButton;
private JCheckBox mobileInstallCheckBox;
private JCheckBox splitApksCheckBox;
private JCheckBox instantRunCheckBox;
private IntegerTextField userIdField;
BlazeAndroidBinaryRunConfigurationStateEditor(@NotNull final Project project) {
this.project = project;
setupUI();
userIdField.setMinValue(0);
activityField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!project.isInitialized()) {
return;
}
// We find all Activity classes in the module for the selected variant (or any of its deps).
final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
PsiClass activityBaseClass = facade.findClass(
AndroidUtils.ACTIVITY_BASE_CLASS_NAME, ProjectScope.getAllScope(project));
if (activityBaseClass == null) {
Messages
.showErrorDialog(panel, AndroidBundle.message("cant.find.activity.class.error"));
return;
}
GlobalSearchScope searchScope = GlobalSearchScope.projectScope(project);
PsiClass initialSelection = facade.findClass(
activityField.getChildComponent().getText(), searchScope);
TreeClassChooser chooser = TreeClassChooserFactory.getInstance(project)
.createInheritanceClassChooser("Select Activity Class",
searchScope, activityBaseClass,
initialSelection, null);
chooser.showDialog();
PsiClass selClass = chooser.getSelected();
if (selClass != null) {
// This must be done because Android represents inner static class paths differently than java.
String qualifiedActivityName = ActivityLocatorUtils.getQualifiedActivityName(selClass);
activityField.getChildComponent().setText(qualifiedActivityName);
}
}
});
ActionListener listener = e -> activityField.setEnabled(launchCustomButton.isSelected());
launchCustomButton.addActionListener(listener);
launchDefaultButton.addActionListener(listener);
launchNothingButton.addActionListener(listener);
instantRunCheckBox.setVisible(InstantRunExperiment.INSTANT_RUN_ENABLED.getValue());
/**
* Only one of mobile-install and instant run can be selected at any one time
*/
mobileInstallCheckBox.addActionListener(e -> {
if (mobileInstallCheckBox.isSelected()) {
instantRunCheckBox.setSelected(false);
}
});
instantRunCheckBox.addActionListener(e -> {
if (instantRunCheckBox.isSelected()) {
mobileInstallCheckBox.setSelected(false);
}
});
mobileInstallCheckBox.addActionListener(e -> splitApksCheckBox.setVisible(mobileInstallCheckBox.isSelected()));
}
@Override
public void resetFrom(BlazeAndroidBinaryRunConfiguration configuration) {
BlazeAndroidBinaryRunConfigurationState configState = configuration.getConfigState();
boolean launchSpecificActivity = configState.MODE.equals(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
if (configState.MODE.equals(BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY)) {
launchDefaultButton.setSelected(true);
}
else if (launchSpecificActivity) {
launchCustomButton.setSelected(true);
}
else {
launchNothingButton.setSelected(true);
}
activityField.setEnabled(launchSpecificActivity);
if (launchSpecificActivity) {
activityField.getChildComponent().setText(configState.ACTIVITY_CLASS);
}
mobileInstallCheckBox.setSelected(configState.isMobileInstall());
splitApksCheckBox.setSelected(configState.isUseSplitApksIfPossible());
instantRunCheckBox.setSelected(configState.isInstantRun());
userIdField.setEnabled(!configState.MODE.equals(BlazeAndroidBinaryRunConfigurationState.DO_NOTHING));
userIdField.setValue(LaunchUtils.getUserIdFromFlags(configState.ACTIVITY_EXTRA_FLAGS));
splitApksCheckBox.setVisible(configState.isMobileInstall());
}
@Override
public Component getComponent() {
return panel;
}
@Override
public void applyTo(BlazeAndroidBinaryRunConfiguration configuration) {
BlazeAndroidBinaryRunConfigurationState configState = configuration.getConfigState();
configState.ACTIVITY_EXTRA_FLAGS = getFlagsFromUserId((Number)userIdField.getValue());
if (launchDefaultButton.isSelected()) {
configState.MODE = BlazeAndroidBinaryRunConfigurationState.LAUNCH_DEFAULT_ACTIVITY;
}
else if (launchCustomButton.isSelected()) {
configState.MODE = BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY;
configState.ACTIVITY_CLASS = activityField.getChildComponent().getText();
}
else {
configState.MODE = BlazeAndroidBinaryRunConfigurationState.DO_NOTHING;
}
configState.setMobileInstall(mobileInstallCheckBox.isSelected());
configState.setUseSplitApksIfPossible(splitApksCheckBox.isSelected());
configState.setInstantRun(instantRunCheckBox.isSelected());
}
@Override
public JComponent getAnchor() {
return null;
}
@Override
public void setAnchor(JComponent anchor) {
}
private void createUIComponents() {
final EditorTextField editorTextField = new LanguageTextField(PlainTextLanguage.INSTANCE,
project, "") {
@Override
protected EditorEx createEditor() {
final EditorEx editor = super.createEditor();
final PsiFile file =
PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file != null) {
DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(file, false);
}
editor.putUserData(ACTIVITY_CLASS_TEXT_FIELD_KEY, BlazeAndroidBinaryRunConfigurationStateEditor.this);
return editor;
}
};
activityField = new ComponentWithBrowseButton<EditorTextField>(editorTextField, null);
}
@NotNull
private static String getFlagsFromUserId(@Nullable Number userId) {
return userId != null ? ("--user " + userId.intValue()) : "";
}
/**
* Initially generated by IntelliJ from a .form file, then checked in as source.
*/
private void setupUI() {
createUIComponents();
panel = new JPanel();
panel.setLayout(new GridLayoutManager(4, 2, new Insets(0, 0, 0, 0), -1, -1));
final JPanel panel1 = new JPanel();
panel1.setLayout(new GridLayoutManager(4, 2, new Insets(0, 0, 0, 0), -1, -1));
panel.add(panel1, new GridConstraints(3, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0,
false));
panel1.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Activity", TitledBorder.DEFAULT_JUSTIFICATION,
TitledBorder.DEFAULT_POSITION,
new Font(panel1.getFont().getName(), panel1.getFont().getStyle(),
panel1.getFont().getSize()), new Color(-16777216)));
launchNothingButton = new JRadioButton();
this.loadButtonText(launchNothingButton,
ResourceBundle.getBundle("messages/AndroidBundle").getString("android.run.configuration.do.nothing.label"));
panel1.add(launchNothingButton, new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
launchDefaultButton = new JRadioButton();
launchDefaultButton.setText("Launch default Activity");
launchDefaultButton.setMnemonic('L');
launchDefaultButton.setDisplayedMnemonicIndex(0);
panel1.add(launchDefaultButton, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
launchCustomButton = new JRadioButton();
launchCustomButton.setText("Launch:");
launchCustomButton.setMnemonic('A');
launchCustomButton.setDisplayedMnemonicIndex(1);
panel1.add(launchCustomButton,
new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
panel1.add(activityField, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
final JLabel label1 = new JLabel();
label1.setText("User ID");
panel1.add(label1,
new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED, null, null, null, 1, false));
userIdField = new IntegerTextField();
panel1.add(userIdField, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0,
false));
mobileInstallCheckBox = new JCheckBox();
mobileInstallCheckBox.setText(" Use blaze mobile-install (go/as-mi)");
panel.add(mobileInstallCheckBox, new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
splitApksCheckBox = new JCheckBox();
splitApksCheckBox.setText(" Use --split_apks where possible");
panel.add(splitApksCheckBox, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
instantRunCheckBox = new JCheckBox();
instantRunCheckBox.setText(" Use InstantRun");
panel.add(instantRunCheckBox, new GridConstraints(2, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
ButtonGroup buttonGroup;
buttonGroup = new ButtonGroup();
buttonGroup.add(launchDefaultButton);
buttonGroup.add(launchCustomButton);
buttonGroup.add(launchNothingButton);
}
/**
* Initially generated by IntelliJ from a .form file.
*/
private void loadButtonText(AbstractButton component, String text) {
StringBuffer result = new StringBuffer();
boolean haveMnemonic = false;
char mnemonic = '\0';
int mnemonicIndex = -1;
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) == '&') {
i++;
if (i == text.length()) break;
if (!haveMnemonic && text.charAt(i) != '&') {
haveMnemonic = true;
mnemonic = text.charAt(i);
mnemonicIndex = result.length();
}
}
result.append(text.charAt(i));
}
component.setText(result.toString());
if (haveMnemonic) {
component.setMnemonic(mnemonic);
component.setDisplayedMnemonicIndex(mnemonicIndex);
}
}
}