| /* |
| * 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.google.common.annotations.VisibleForTesting; |
| |
| 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.intellij.CommonBundle; |
| import com.intellij.ide.actions.TemplateKindCombo; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.actionSystem.DataContext; |
| 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 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; |
| |
| import javax.annotation.Nullable; |
| import javax.swing.*; |
| import javax.swing.event.DocumentEvent; |
| import java.awt.*; |
| import java.util.Collection; |
| import java.util.List; |
| |
| /** |
| * 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); |
| } |
| |
| // 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. |
| myResDirCombo.setVisible(false); |
| myResDirLabel.setVisible(false); |
| Project project = myFacet.getModule().getProject(); |
| 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); |
| } |
| |
| } |