blob: 6c132b07b06de9d2c9f3c5d3f132d9ac27480ec2 [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.resources.actions;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.resources.ResourceConstants;
import com.android.resources.ResourceFolderType;
import com.android.tools.idea.res.ResourceNameValidator;
import com.google.common.annotations.VisibleForTesting;
import com.intellij.CommonBundle;
import com.intellij.ide.actions.TemplateKindCombo;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.ui.ComboboxWithBrowseButton;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.TextFieldWithAutoCompletion;
import com.intellij.ui.TextFieldWithAutoCompletion.StringsCompletionProvider;
import com.intellij.ui.components.JBLabel;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.util.PlatformIcons;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Insets;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import org.jetbrains.android.actions.CreateResourceFileDialogBase;
import org.jetbrains.android.actions.CreateTypedResourceFileAction;
import org.jetbrains.android.actions.ElementCreatingValidator;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.uipreview.DeviceConfiguratorPanel;
import org.jetbrains.android.util.AndroidBundle;
/**
* Dialog to decide where and how to create a resource file of a given type (which base res/
* directory, which subdirectory, and how to name the new file).
*/
public class BlazeCreateResourceFileDialog extends CreateResourceFileDialogBase {
private JTextField myFileNameField;
private TemplateKindCombo myResourceTypeCombo;
private JPanel myPanel;
private JLabel myUpDownHint;
private JLabel myResTypeLabel;
private JPanel myDeviceConfiguratorWrapper;
private JBLabel myErrorLabel;
private JTextField myDirectoryNameTextField;
private JPanel myRootElementFieldWrapper;
private JBLabel myRootElementLabel;
private JLabel myFileNameLabel;
private ComboboxWithBrowseButton myResDirCombo;
private JBLabel myResDirLabel;
private TextFieldWithAutoCompletion<String> myRootElementField;
private ElementCreatingValidator myValidator;
private ValidatorFactory myValidatorFactory;
private final DeviceConfiguratorPanel myDeviceConfiguratorPanel;
private final AndroidFacet myFacet;
private PsiDirectory myResDirectory;
public BlazeCreateResourceFileDialog(
AndroidFacet facet,
Collection<CreateTypedResourceFileAction> actions,
ResourceFolderType folderType,
String filename,
String rootElement,
FolderConfiguration folderConfiguration,
boolean chooseFileName,
boolean chooseModule,
PsiDirectory resDirectory,
DataContext dataContext,
ValidatorFactory validatorFactory) {
super(facet.getModule().getProject());
setupUi();
myFacet = facet;
myResDirectory = resDirectory;
myValidatorFactory = validatorFactory;
myResTypeLabel.setLabelFor(myResourceTypeCombo);
myResourceTypeCombo.registerUpDownHint(myFileNameField);
myUpDownHint.setIcon(PlatformIcons.UP_DOWN_ARROWS);
String selectedTemplate = setupSubActions(actions, myResourceTypeCombo, folderType);
myDeviceConfiguratorPanel =
setupDeviceConfigurationPanel(myDirectoryNameTextField, myResourceTypeCombo, myErrorLabel);
if (folderConfiguration != null) {
myDeviceConfiguratorPanel.init(folderConfiguration);
}
myResourceTypeCombo
.getComboBox()
.addActionListener(
e -> {
myDeviceConfiguratorPanel.applyEditors();
updateRootElementTextField();
});
if (folderType != null && selectedTemplate != null) {
final boolean v = folderType == ResourceFolderType.LAYOUT;
myRootElementLabel.setVisible(v);
myRootElementFieldWrapper.setVisible(v);
myResTypeLabel.setVisible(false);
myResourceTypeCombo.setVisible(false);
myUpDownHint.setVisible(false);
myResourceTypeCombo.setSelectedName(selectedTemplate);
} else {
// Select values by default if not otherwise specified
myResourceTypeCombo.setSelectedName(ResourceConstants.FD_RES_VALUES);
}
boolean validateImmediately = false;
if (filename != null && getNameError(filename) != null) {
chooseFileName = true;
validateImmediately = true;
}
if (filename != null) {
if (!chooseFileName) {
myFileNameField.setVisible(false);
myFileNameLabel.setVisible(false);
}
myFileNameField.setText(filename);
}
Project project = myFacet.getModule().getProject();
// Set up UI to choose the base directory if needed (use context to prune selection).
// There may be a resource directory already pre-selected, in which case hide the UI by default.
myResDirLabel.setVisible(false);
myResDirCombo.setVisible(false);
myResDirCombo.addBrowseFolderListener(
project, FileChooserDescriptorFactory.createSingleFolderDescriptor());
if (myResDirectory == null) {
// Try to figure out from context (e.g., right click in project view).
VirtualFile contextFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
if (contextFile != null) {
PsiManager manager = PsiManager.getInstance(project);
VirtualFile virtualDirectory =
BlazeCreateResourceUtils.getResDirFromDataContext(contextFile);
PsiDirectory directory =
virtualDirectory != null ? manager.findDirectory(virtualDirectory) : null;
if (directory != null) {
myResDirectory = directory;
} else {
// As a last resort, if we have poor context,
// e.g., from File > New w/ a .java file open, set up the UI.
BlazeCreateResourceUtils.setupResDirectoryChoices(
project, contextFile, myResDirLabel, myResDirCombo);
}
} else {
// As a last resort, if we have no context, set up the UI.
BlazeCreateResourceUtils.setupResDirectoryChoices(
project, null, myResDirLabel, myResDirCombo);
}
}
myDeviceConfiguratorPanel.updateAll();
myDeviceConfiguratorWrapper.add(myDeviceConfiguratorPanel, BorderLayout.CENTER);
updateOkAction();
updateRootElementTextField();
if (rootElement != null) {
myRootElementLabel.setVisible(false);
myRootElementFieldWrapper.setVisible(false);
myRootElementField.setText(rootElement);
}
init();
setTitle(AndroidBundle.message("new.resource.dialog.title"));
myFileNameField
.getDocument()
.addDocumentListener(
new DocumentAdapter() {
@Override
public void textChanged(DocumentEvent event) {
validateName();
}
});
myResourceTypeCombo.getComboBox().addActionListener(actionEvent -> validateName());
if (validateImmediately) {
validateName();
}
}
@Override
protected void updateOkAction() {
boolean enabled = myDirectoryNameTextField.getText().length() > 0;
enabled = enabled && getNameError(myFileNameField.getText()) == null;
setOKActionEnabled(enabled);
}
@Nullable
private String getNameError(String fileName) {
String typeName = myResourceTypeCombo.getSelectedName();
if (typeName != null) {
ResourceFolderType type = ResourceFolderType.getFolderType(typeName);
if (type != null) {
ResourceNameValidator validator = ResourceNameValidator.create(true, type);
return validator.getErrorText(fileName);
}
}
return null;
}
private void validateName() {
setErrorText(getNameError(myFileNameField.getText()));
updateOkAction();
}
private void updateRootElementTextField() {
final CreateTypedResourceFileAction action = getSelectedAction(myResourceTypeCombo);
if (action != null) {
final List<String> allowedTagNames = action.getSortedAllowedTagNames(myFacet);
myRootElementField =
new TextFieldWithAutoCompletion<>(
myFacet.getModule().getProject(),
new StringsCompletionProvider(allowedTagNames, null),
true,
null);
myRootElementField.setEnabled(allowedTagNames.size() > 1);
myRootElementField.setText(!action.isChooseTagName() ? action.getDefaultRootTag() : "");
myRootElementFieldWrapper.removeAll();
myRootElementFieldWrapper.add(myRootElementField, BorderLayout.CENTER);
myRootElementLabel.setLabelFor(myRootElementField);
}
}
@VisibleForTesting
@Override
public String getFileName() {
return myFileNameField.getText().trim();
}
@Override
protected void doOKAction() {
String fileName = myFileNameField.getText().trim();
final CreateTypedResourceFileAction action = getSelectedAction(myResourceTypeCombo);
assert action != null;
if (fileName.length() == 0) {
Messages.showErrorDialog(
myPanel,
AndroidBundle.message("file.name.not.specified.error"),
CommonBundle.getErrorTitle());
return;
}
String rootElement = getRootElement();
if (!action.isChooseTagName() && rootElement.length() == 0) {
Messages.showErrorDialog(
myPanel,
AndroidBundle.message("root.element.not.specified.error"),
CommonBundle.getErrorTitle());
return;
}
final String subdirName = getSubdirName();
if (subdirName.length() == 0) {
Messages.showErrorDialog(
myPanel,
AndroidBundle.message("directory.not.specified.error"),
CommonBundle.getErrorTitle());
return;
}
final String errorMessage = getNameError(fileName);
if (errorMessage != null) {
Messages.showErrorDialog(myPanel, errorMessage, CommonBundle.getErrorTitle());
return;
}
PsiDirectory resDir = getResourceDirectory();
if (resDir == null) {
Messages.showErrorDialog(
myPanel,
AndroidBundle.message("check.resource.dir.error", myFacet.getModule()),
CommonBundle.getErrorTitle());
super.doOKAction();
return;
}
myValidator = myValidatorFactory.create(resDir, subdirName, rootElement);
if (myValidator.checkInput(fileName) && myValidator.canClose(fileName)) {
super.doOKAction();
}
}
@Override
public PsiElement[] getCreatedElements() {
return myValidator != null ? myValidator.getCreatedElements() : PsiElement.EMPTY_ARRAY;
}
@Override
protected String getDimensionServiceKey() {
return "BlazeCreateResourceFileDialog";
}
@Nullable
private PsiDirectory getResourceDirectory() {
if (myResDirectory != null) {
return myResDirectory;
}
return BlazeCreateResourceUtils.getResDirFromUI(
myFacet.getModule().getProject(), myResDirCombo);
}
private String getSubdirName() {
return myDirectoryNameTextField.getText().trim();
}
protected String getRootElement() {
return myRootElementField.getText().trim();
}
@Override
protected JComponent createCenterPanel() {
return myPanel;
}
@Override
public JComponent getPreferredFocusedComponent() {
String name = myFileNameField.getText();
if (name.length() == 0 || getNameError(name) != null) {
return myFileNameField;
} else if (myResourceTypeCombo.isVisible()) {
return myResourceTypeCombo;
} else if (myRootElementFieldWrapper.isVisible()) {
return myRootElementField;
}
return myDirectoryNameTextField;
}
/** Initially generated by IntelliJ from a .form file. */
private void setupUi() {
myPanel = new JPanel();
myPanel.setLayout(new GridLayoutManager(7, 3, new Insets(0, 0, 0, 0), -1, -1));
myPanel.setPreferredSize(new Dimension(800, 400));
myFileNameLabel = new JLabel();
myFileNameLabel.setText("File name:");
myFileNameLabel.setDisplayedMnemonic('F');
myFileNameLabel.setDisplayedMnemonicIndex(0);
myPanel.add(
myFileNameLabel,
new GridConstraints(
0,
0,
1,
1,
GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_NONE,
GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED,
null,
null,
null,
0,
false));
myFileNameField = new JTextField();
myPanel.add(
myFileNameField,
new GridConstraints(
0,
1,
1,
1,
GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_WANT_GROW,
GridConstraints.SIZEPOLICY_FIXED,
null,
new Dimension(150, -1),
null,
0,
false));
myResTypeLabel = new JLabel();
myResTypeLabel.setText("Resource type:");
myResTypeLabel.setDisplayedMnemonic('R');
myResTypeLabel.setDisplayedMnemonicIndex(0);
myPanel.add(
myResTypeLabel,
new GridConstraints(
1,
0,
1,
1,
GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_NONE,
GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED,
null,
null,
null,
0,
false));
myUpDownHint = new JLabel();
myUpDownHint.setToolTipText("Pressing Up or Down arrows while in editor changes the kind");
myPanel.add(
myUpDownHint,
new GridConstraints(
0,
2,
1,
1,
GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_NONE,
GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED,
null,
null,
null,
0,
false));
myResourceTypeCombo = new TemplateKindCombo();
myPanel.add(
myResourceTypeCombo,
new GridConstraints(
1,
1,
1,
1,
GridConstraints.ANCHOR_CENTER,
GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_FIXED,
null,
null,
null,
0,
false));
myDeviceConfiguratorWrapper = new JPanel();
myDeviceConfiguratorWrapper.setLayout(new BorderLayout(0, 0));
myPanel.add(
myDeviceConfiguratorWrapper,
new GridConstraints(
5,
0,
1,
3,
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));
myErrorLabel = new JBLabel();
myPanel.add(
myErrorLabel,
new GridConstraints(
6,
0,
1,
3,
GridConstraints.ANCHOR_CENTER,
GridConstraints.FILL_BOTH,
GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED,
null,
null,
null,
0,
false));
final JBLabel jBLabel1 = new JBLabel();
jBLabel1.setText("Sub-directory:");
jBLabel1.setDisplayedMnemonic('Y');
jBLabel1.setDisplayedMnemonicIndex(12);
myPanel.add(
jBLabel1,
new GridConstraints(
4,
0,
1,
1,
GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_NONE,
GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED,
null,
null,
null,
0,
false));
myDirectoryNameTextField = new JTextField();
myDirectoryNameTextField.setEditable(true);
myDirectoryNameTextField.setEnabled(true);
myPanel.add(
myDirectoryNameTextField,
new GridConstraints(
4,
1,
1,
1,
GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_WANT_GROW,
GridConstraints.SIZEPOLICY_FIXED,
null,
new Dimension(150, -1),
null,
0,
false));
myRootElementLabel = new JBLabel();
myRootElementLabel.setText("Root element:");
myRootElementLabel.setDisplayedMnemonic('E');
myRootElementLabel.setDisplayedMnemonicIndex(5);
myPanel.add(
myRootElementLabel,
new GridConstraints(
2,
0,
1,
1,
GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_NONE,
GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED,
null,
null,
null,
0,
false));
myRootElementFieldWrapper = new JPanel();
myRootElementFieldWrapper.setLayout(new BorderLayout(0, 0));
myPanel.add(
myRootElementFieldWrapper,
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));
myResDirLabel = new JBLabel();
myResDirLabel.setText("Base directory:");
myResDirLabel.setDisplayedMnemonic('B');
myResDirLabel.setDisplayedMnemonicIndex(0);
myPanel.add(
myResDirLabel,
new GridConstraints(
3,
0,
1,
1,
GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_NONE,
GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED,
null,
null,
null,
0,
false));
myResDirCombo = new ComboboxWithBrowseButton();
myPanel.add(
myResDirCombo,
new GridConstraints(
3,
1,
1,
1,
GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_FIXED,
null,
null,
null,
0,
false));
myFileNameLabel.setLabelFor(myFileNameField);
jBLabel1.setLabelFor(myDirectoryNameTextField);
}
}