/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* 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 org.jetbrains.android.actions;
import com.android.SdkConstants;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.tools.idea.rendering.ResourceHelper;
import com.intellij.CommonBundle;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.fileChooser.actions.VirtualFileDeleteProvider;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.application.options.ModulesComboBox;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.util.io.FileUtil;
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.*;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.hash.HashMap;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.dom.resources.Resources;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
/**
* @author Eugene.Kudelevsky
*/
public class CreateXmlResourceDialog extends DialogWrapper {
private JPanel myPanel;
private JTextField myNameField;
private ModulesComboBox myModuleCombo;
private JBLabel myModuleLabel;
private JPanel myDirectoriesPanel;
private JBLabel myDirectoriesLabel;
private JTextField myValueField;
private JBLabel myValueLabel;
private JBLabel myNameLabel;
private JComboBox myFileNameCombo;
private final Module myModule;
private final ResourceType myResourceType;
private Map<String, JCheckBox> myCheckBoxes = Collections.emptyMap();
private String[] myDirNames = ArrayUtil.EMPTY_STRING_ARRAY;
private final CheckBoxList myDirectoriesList;
private VirtualFile myResourceDir;
public CreateXmlResourceDialog(@NotNull Module module,
@NotNull ResourceType resourceType,
@Nullable String predefinedName,
@Nullable String predefinedValue,
boolean chooseName) {
this(module, resourceType, predefinedName, predefinedValue, chooseName, null);
}
public CreateXmlResourceDialog(@NotNull Module module,
@NotNull ResourceType resourceType,
@Nullable String predefinedName,
@Nullable String predefinedValue,
boolean chooseName,
@Nullable VirtualFile defaultFile) {
super(module.getProject());
myResourceType = resourceType;
if (chooseName) {
predefinedName = ResourceHelper.prependResourcePrefix(module, predefinedName);
}
if (predefinedName != null && predefinedName.length() > 0) {
if (!chooseName) {
myNameLabel.setVisible(false);
myNameField.setVisible(false);
}
myNameField.setText(predefinedName);
}
if (predefinedValue != null && predefinedValue.length() > 0) {
myValueLabel.setVisible(false);
myValueField.setVisible(false);
myValueField.setText(predefinedValue);
}
final Set<Module> modulesSet = new HashSet<Module>();
modulesSet.add(module);
for (AndroidFacet depFacet : AndroidUtils.getAllAndroidDependencies(module, true)) {
modulesSet.add(depFacet.getModule());
}
assert modulesSet.size() > 0;
if (modulesSet.size() == 1) {
myModule = module;
myModuleLabel.setVisible(false);
myModuleCombo.setVisible(false);
}
else {
myModule = null;
myModuleCombo.setModules(modulesSet);
myModuleCombo.setSelectedModule(module);
}
if (defaultFile == null) {
final String defaultFileName = AndroidResourceUtil.getDefaultResourceFileName(resourceType);
if (defaultFileName != null) {
myFileNameCombo.getEditor().setItem(defaultFileName);
}
}
myDirectoriesList = new CheckBoxList();
myDirectoriesLabel.setLabelFor(myDirectoriesList);
final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myDirectoriesList);
decorator.setEditAction(null);
decorator.disableUpDownActions();
decorator.setAddAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
doAddNewDirectory();
}
});
decorator.setRemoveAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
doDeleteDirectory();
}
});
final AnActionButton selectAll = new AnActionButton("Select All", null, PlatformIcons.SELECT_ALL_ICON) {
@Override
public void actionPerformed(AnActionEvent e) {
doSelectAllDirs();
}
};
decorator.addExtraAction(selectAll);
final AnActionButton unselectAll = new AnActionButton("Unselect All", null, PlatformIcons.UNSELECT_ALL_ICON) {
@Override
public void actionPerformed(AnActionEvent e) {
doUnselectAllDirs();
}
};
decorator.addExtraAction(unselectAll);
myDirectoriesPanel.add(decorator.createPanel());
updateDirectories(true);
myModuleCombo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateDirectories(true);
}
});
final JCheckBox valuesCheckBox = myCheckBoxes.get(SdkConstants.FD_RES_VALUES);
if (valuesCheckBox != null) {
valuesCheckBox.setSelected(true);
}
if (defaultFile != null) {
resetFromFile(defaultFile, module.getProject());
}
init();
}
private void resetFromFile(@NotNull VirtualFile file, @NotNull Project project) {
final Module moduleForFile = ModuleUtilCore.findModuleForFile(file, project);
if (moduleForFile == null) {
return;
}
final VirtualFile parent = file.getParent();
if (parent == null) {
return;
}
if (myModule == null) {
final Module prev = myModuleCombo.getSelectedModule();
myModuleCombo.setSelectedItem(moduleForFile);
if (!moduleForFile.equals(myModuleCombo.getSelectedItem())) {
myModuleCombo.setSelectedModule(prev);
return;
}
}
else if (!myModule.equals(moduleForFile)) {
return;
}
final JCheckBox checkBox = myCheckBoxes.get(parent.getName());
if (checkBox == null) {
return;
}
for (JCheckBox checkBox1 : myCheckBoxes.values()) {
checkBox1.setSelected(false);
}
checkBox.setSelected(true);
myFileNameCombo.getEditor().setItem(file.getName());
}
private void doDeleteDirectory() {
if (myResourceDir == null) {
return;
}
final int selectedIndex = myDirectoriesList.getSelectedIndex();
if (selectedIndex < 0) {
return;
}
final String selectedDirName = myDirNames[selectedIndex];
final VirtualFile selectedDir = myResourceDir.findChild(selectedDirName);
if (selectedDir == null) {
return;
}
final VirtualFileDeleteProvider provider = new VirtualFileDeleteProvider();
provider.deleteElement(new DataContext() {
@Override
public Object getData(@NonNls String dataId) {
if (CommonDataKeys.VIRTUAL_FILE_ARRAY.getName().equals(dataId)) {
return new VirtualFile[] {selectedDir};
}
else {
return null;
}
}
});
updateDirectories(false);
}
private void doSelectAllDirs() {
for (JCheckBox checkBox : myCheckBoxes.values()) {
checkBox.setSelected(true);
}
myDirectoriesList.repaint();
}
private void doUnselectAllDirs() {
for (JCheckBox checkBox : myCheckBoxes.values()) {
checkBox.setSelected(false);
}
myDirectoriesList.repaint();
}
private void doAddNewDirectory() {
if (myResourceDir == null) {
return;
}
final Module module = getModule();
if (module == null) {
return;
}
final Project project = module.getProject();
final PsiDirectory psiResDir = PsiManager.getInstance(project).findDirectory(myResourceDir);
if (psiResDir != null) {
final PsiElement[] createdElements = new CreateResourceDirectoryAction(ResourceFolderType.VALUES).invokeDialog(project, psiResDir);
if (createdElements.length > 0) {
updateDirectories(false);
}
}
}
private void updateDirectories(boolean updateFileCombo) {
final Module module = getModule();
List<VirtualFile> valuesDirs = Collections.emptyList();
if (module != null) {
final AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null) {
myResourceDir = facet.getPrimaryResourceDir();
if (myResourceDir != null) {
valuesDirs = AndroidResourceUtil.getResourceSubdirs(ResourceFolderType.VALUES.getName(), new VirtualFile[]{myResourceDir});
}
}
}
Collections.sort(valuesDirs, new Comparator<VirtualFile>() {
@Override
public int compare(VirtualFile f1, VirtualFile f2) {
return f1.getName().compareTo(f2.getName());
}
});
final Map<String, JCheckBox> oldCheckBoxes = myCheckBoxes;
final int selectedIndex = myDirectoriesList.getSelectedIndex();
final String selectedDirName = selectedIndex >= 0 ? myDirNames[selectedIndex] : null;
final List<JCheckBox> checkBoxList = new ArrayList<JCheckBox>();
myCheckBoxes = new HashMap<String, JCheckBox>();
myDirNames = new String[valuesDirs.size()];
int newSelectedIndex = -1;
int i = 0;
for (VirtualFile dir : valuesDirs) {
final String dirName = dir.getName();
final JCheckBox oldCheckBox = oldCheckBoxes.get(dirName);
final boolean selected = oldCheckBox != null && oldCheckBox.isSelected();
final JCheckBox checkBox = new JCheckBox(dirName, selected);
checkBoxList.add(checkBox);
myCheckBoxes.put(dirName, checkBox);
myDirNames[i] = dirName;
if (dirName.equals(selectedDirName)) {
newSelectedIndex = i;
}
i++;
}
myDirectoriesList.setModel(new CollectionListModel<JCheckBox>(checkBoxList));
if (newSelectedIndex >= 0) {
myDirectoriesList.setSelectedIndex(newSelectedIndex);
}
if (checkBoxList.size() == 1) {
checkBoxList.get(0).setSelected(true);
}
if (updateFileCombo) {
final Object oldItem = myFileNameCombo.getEditor().getItem();
final Set<String> fileNameSet = new HashSet<String>();
for (VirtualFile valuesDir : valuesDirs) {
for (VirtualFile file : valuesDir.getChildren()) {
fileNameSet.add(file.getName());
}
}
final List<String> fileNames = new ArrayList<String>(fileNameSet);
Collections.sort(fileNames);
myFileNameCombo.setModel(new DefaultComboBoxModel(fileNames.toArray()));
myFileNameCombo.getEditor().setItem(oldItem);
}
}
@Override
protected ValidationInfo doValidate() {
final String resourceName = getResourceName();
final Module selectedModule = getModule();
final List<String> directoryNames = getDirNames();
final String fileName = getFileName();
if (resourceName.length() == 0) {
return new ValidationInfo("specify resource name", myNameField);
}
else if (!AndroidResourceUtil.isCorrectAndroidResourceName(resourceName)) {
return new ValidationInfo(resourceName + " is not correct resource name", myNameField);
}
else if (fileName.length() == 0) {
return new ValidationInfo("specify file name", myFileNameCombo);
}
else if (selectedModule == null) {
return new ValidationInfo("specify module", myModuleCombo);
}
else if (directoryNames.size() == 0) {
return new ValidationInfo("choose directories", myDirectoriesList);
} else if (resourceName.equals(ResourceHelper.prependResourcePrefix(myModule, null))) {
return new ValidationInfo("specify more than resource prefix", myNameField);
}
final ValidationInfo info = checkIfResourceAlreadyExists(selectedModule, resourceName, myResourceType, directoryNames, fileName);
if (info != null) {
return info;
}
return null;
}
@Nullable
public static ValidationInfo checkIfResourceAlreadyExists(@NotNull Module selectedModule,
@NotNull String resourceName,
@NotNull ResourceType resourceType,
@NotNull List<String> dirNames,
@NotNull String fileName) {
if (resourceName.length() == 0 ||
dirNames.size() == 0 ||
fileName.length() == 0) {
return null;
}
final AndroidFacet facet = AndroidFacet.getInstance(selectedModule);
final VirtualFile resourceDir = facet != null ? facet.getPrimaryResourceDir() : null;
if (resourceDir == null) {
return null;
}
for (String directoryName : dirNames) {
final VirtualFile resourceSubdir = resourceDir.findChild(directoryName);
if (resourceSubdir == null) {
continue;
}
final VirtualFile resFile = resourceSubdir.findChild(fileName);
if (resFile == null) {
continue;
}
if (resFile.getFileType() != StdFileTypes.XML) {
return new ValidationInfo("File " + FileUtil.toSystemDependentName(resFile.getPath()) + " is not XML file");
}
final Resources resources = AndroidUtils.loadDomElement(selectedModule, resFile, Resources.class);
if (resources == null) {
return new ValidationInfo(AndroidBundle.message("not.resource.file.error", FileUtil.toSystemDependentName(resFile.getPath())));
}
for (ResourceElement element : AndroidResourceUtil.getValueResourcesFromElement(resourceType.getName(), resources)) {
if (resourceName.equals(element.getName().getValue())) {
return new ValidationInfo("resource '" + resourceName + "' already exists in " + FileUtil.toSystemDependentName(
resFile.getPath()));
}
}
}
return null;
}
@Override
public JComponent getPreferredFocusedComponent() {
String name = myNameField.getText();
if (name.length() == 0 || name.equals(ResourceHelper.prependResourcePrefix(myModule, null))) {
return myNameField;
}
else if (myValueField.isVisible()) {
return myValueField;
}
else if (myModuleCombo.isVisible()) {
return myModuleCombo;
}
else {
return myFileNameCombo;
}
}
@Override
protected void doOKAction() {
final String resourceName = getResourceName();
final String fileName = getFileName();
final List<String> dirNames = getDirNames();
final Module module = getModule();
if (resourceName.length() == 0) {
Messages.showErrorDialog(myPanel, "Resource name is not specified", CommonBundle.getErrorTitle());
}
else if (!AndroidResourceUtil.isCorrectAndroidResourceName(resourceName)) {
Messages.showErrorDialog(myPanel, resourceName + " is not correct resource name", CommonBundle.getErrorTitle());
}
else if (fileName.length() == 0) {
Messages.showErrorDialog(myPanel, "File name is not specified", CommonBundle.getErrorTitle());
}
else if (dirNames.size() == 0) {
Messages.showErrorDialog(myPanel, "Directories are not selected", CommonBundle.getErrorTitle());
}
else if (module == null) {
Messages.showErrorDialog(myPanel, "Module is not specified", CommonBundle.getErrorTitle());
}
else {
super.doOKAction();
}
}
@Override
protected String getDimensionServiceKey() {
return "AndroidCreateXmlResourceDialog";
}
@NotNull
public String getResourceName() {
return myNameField.getText().trim();
}
@NotNull
public List<String> getDirNames() {
final List<String> selectedDirs = new ArrayList<String>();
for (Map.Entry<String, JCheckBox> entry : myCheckBoxes.entrySet()) {
if (entry.getValue().isSelected()) {
selectedDirs.add(entry.getKey());
}
}
return selectedDirs;
}
@NotNull
public String getFileName() {
return ((String)myFileNameCombo.getEditor().getItem()).trim();
}
@NotNull
public String getName() {
return myNameField.getText().trim();
}
@NotNull
public String getValue() {
return myValueField.getText().trim();
}
@Nullable
public Module getModule() {
return myModule != null ? myModule : myModuleCombo.getSelectedModule();
}
@Override
protected JComponent createCenterPanel() {
return myPanel;
}
}