| /* |
| * Copyright 2000-2014 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. |
| */ |
| |
| /** |
| * @author cdr |
| */ |
| package com.intellij.lang.properties.references; |
| |
| import com.intellij.codeInsight.CodeInsightBundle; |
| import com.intellij.ide.util.PropertiesComponent; |
| import com.intellij.ide.util.TreeFileChooser; |
| import com.intellij.ide.util.TreeFileChooserFactory; |
| import com.intellij.lang.properties.IProperty; |
| import com.intellij.lang.properties.LastSelectedPropertiesFileStore; |
| import com.intellij.lang.properties.psi.PropertiesFile; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileTypes.FileType; |
| import com.intellij.openapi.fileTypes.FileTypeManager; |
| import com.intellij.openapi.fileTypes.StdFileTypes; |
| import com.intellij.openapi.help.HelpManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.ThrowableComputable; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VfsUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiManager; |
| import com.intellij.ui.DocumentAdapter; |
| import com.intellij.ui.GuiUtils; |
| import com.intellij.ui.TextFieldWithHistory; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| |
| import javax.swing.*; |
| import javax.swing.event.DocumentEvent; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.io.File; |
| import java.io.IOException; |
| import java.text.Normalizer; |
| import java.util.*; |
| import java.util.List; |
| import java.util.regex.Pattern; |
| |
| public class I18nizeQuickFixDialog extends DialogWrapper implements I18nizeQuickFixModel { |
| protected static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.i18n.I18nizeQuickFixDialog"); |
| |
| private static final Pattern PATTERN = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); |
| |
| private JTextField myValue; |
| private JComboBox myKey; |
| private final TextFieldWithHistory myPropertiesFile; |
| protected JPanel myPanel; |
| private JCheckBox myUseResourceBundle; |
| protected final Project myProject; |
| protected final PsiFile myContext; |
| |
| private JPanel myPropertiesFilePanel; |
| protected JPanel myExtensibilityPanel; |
| |
| protected final String myDefaultPropertyValue; |
| protected final DialogCustomization myCustomization; |
| |
| public static class DialogCustomization { |
| private final String title; |
| private final boolean suggestExistingProperties; |
| private final boolean focusValueComponent; |
| private final List<PropertiesFile> propertiesFiles; |
| private final String suggestedName; |
| |
| public DialogCustomization(String title, boolean suggestExistingProperties, boolean focusValueComponent, |
| List<PropertiesFile> propertiesFiles, |
| String suggestedName) { |
| this.title = title; |
| this.suggestExistingProperties = suggestExistingProperties; |
| this.focusValueComponent = focusValueComponent; |
| this.propertiesFiles = propertiesFiles; |
| this.suggestedName = suggestedName; |
| } |
| |
| public DialogCustomization() { |
| this(null, true, false, null, null); |
| } |
| |
| public String getSuggestedName() { |
| return suggestedName; |
| } |
| } |
| |
| public I18nizeQuickFixDialog(@NotNull Project project, |
| @NotNull final PsiFile context, |
| String defaultPropertyValue, |
| DialogCustomization customization |
| ) { |
| this(project, context, defaultPropertyValue, customization, false); |
| } |
| |
| protected I18nizeQuickFixDialog(@NotNull Project project, |
| @NotNull final PsiFile context, |
| String defaultPropertyValue, |
| DialogCustomization customization, |
| boolean ancestorResponsible) { |
| super(false); |
| myProject = project; |
| myContext = context; |
| |
| myDefaultPropertyValue = defaultPropertyValue; |
| myCustomization = customization != null ? customization:new DialogCustomization(); |
| setTitle(myCustomization.title != null ? myCustomization.title:CodeInsightBundle.message("i18nize.dialog.title")); |
| |
| myPropertiesFile = new TextFieldWithHistory(); |
| myPropertiesFile.setHistorySize(-1); |
| myPropertiesFilePanel.add(GuiUtils.constructFieldWithBrowseButton(myPropertiesFile, new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| TreeFileChooserFactory chooserFactory = TreeFileChooserFactory.getInstance(myProject); |
| final PropertiesFile propertiesFile = getPropertiesFile(); |
| TreeFileChooser fileChooser = chooserFactory.createFileChooser( |
| CodeInsightBundle.message("i18nize.dialog.property.file.chooser.title"), propertiesFile != null ? propertiesFile.getContainingFile() : null, StdFileTypes.PROPERTIES, null); |
| fileChooser.showDialog(); |
| PsiFile selectedFile = fileChooser.getSelectedFile(); |
| if (selectedFile == null) return; |
| myPropertiesFile.setText(FileUtil.toSystemDependentName(selectedFile.getVirtualFile().getPath())); |
| } |
| }), BorderLayout.CENTER); |
| |
| myPropertiesFile.addDocumentListener(new DocumentAdapter() { |
| @Override |
| protected void textChanged(DocumentEvent e) { |
| propertiesFileChanged(); |
| somethingChanged(); |
| } |
| }); |
| |
| getKeyTextField().getDocument().addDocumentListener(new DocumentAdapter() { |
| @Override |
| protected void textChanged(DocumentEvent e) { |
| somethingChanged(); |
| } |
| }); |
| |
| myValue.getDocument().addDocumentListener(new DocumentAdapter() { |
| @Override |
| protected void textChanged(DocumentEvent e) { |
| somethingChanged(); |
| } |
| }); |
| |
| |
| @NonNls final String KEY = "I18NIZE_DIALOG_USE_RESOURCE_BUNDLE"; |
| final boolean useBundleByDefault = |
| !PropertiesComponent.getInstance().isValueSet(KEY) || PropertiesComponent.getInstance().isTrueValue(KEY); |
| myUseResourceBundle.setSelected(useBundleByDefault); |
| myUseResourceBundle.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| PropertiesComponent.getInstance().setValue(KEY, Boolean.valueOf(myUseResourceBundle.isSelected()).toString()); |
| } |
| }); |
| |
| if (!ancestorResponsible) init(); |
| } |
| |
| @Override |
| protected void init() { |
| populatePropertiesFiles(); |
| propertiesFileChanged(); |
| somethingChanged(); |
| setKeyValueEditBoxes(); |
| |
| super.init(); |
| } |
| |
| private JTextField getKeyTextField() { |
| return (JTextField)myKey.getEditor().getEditorComponent(); |
| } |
| |
| @NotNull |
| protected List<String> getExistingValueKeys(String value) { |
| if(!myCustomization.suggestExistingProperties) { |
| return Collections.emptyList(); |
| } |
| final ArrayList<String> result = new ArrayList<String>(); |
| |
| // check if property value already exists among properties file values and suggest corresponding key |
| PropertiesFile propertiesFile = getPropertiesFile(); |
| if (propertiesFile != null) { |
| for (IProperty property : propertiesFile.getProperties()) { |
| if (Comparing.strEqual(property.getValue(), value)) { |
| result.add(0, property.getUnescapedKey()); |
| } |
| } |
| } |
| return result; |
| } |
| |
| protected String suggestPropertyKey(String value) { |
| if (myCustomization.suggestedName != null) { |
| return myCustomization.suggestedName; |
| } |
| |
| // suggest property key not existing in this file |
| String key = defaultSuggestPropertyKey(value); |
| value = PATTERN.matcher(Normalizer.normalize(value, Normalizer.Form.NFD)).replaceAll(""); |
| if (key == null) { |
| final StringBuilder result = new StringBuilder(); |
| boolean insertDotBeforeNextWord = false; |
| for (int i = 0; i < value.length(); i++) { |
| final char c = value.charAt(i); |
| if (Character.isLetterOrDigit(c)) { |
| if (insertDotBeforeNextWord) { |
| result.append('.'); |
| } |
| result.append(Character.toLowerCase(c)); |
| insertDotBeforeNextWord = false; |
| } |
| else if (c == '&') { //do not insert dot if there is letter after the amp |
| if (insertDotBeforeNextWord) continue; |
| if (i == value.length() - 1) { |
| continue; |
| } |
| if (Character.isLetter(value.charAt(i + 1))) { |
| continue; |
| } |
| insertDotBeforeNextWord = true; |
| } |
| else { |
| if (result.length() > 0) { |
| insertDotBeforeNextWord = true; |
| } |
| } |
| } |
| key = result.toString(); |
| } |
| |
| PropertiesFile propertiesFile = getPropertiesFile(); |
| if (propertiesFile != null) { |
| if (propertiesFile.findPropertyByKey(key) == null) return key; |
| |
| int suffix = 1; |
| while (propertiesFile.findPropertyByKey(key + suffix) != null) { |
| suffix++; |
| } |
| return key + suffix; |
| } |
| else { |
| return key; |
| } |
| } |
| |
| protected String defaultSuggestPropertyKey(String value) { |
| return null; |
| } |
| |
| private void propertiesFileChanged() { |
| PropertiesFile propertiesFile = getPropertiesFile(); |
| boolean hasResourceBundle = |
| propertiesFile != null && propertiesFile.getResourceBundle().getPropertiesFiles().size() > 1; |
| myUseResourceBundle.setEnabled(hasResourceBundle); |
| } |
| |
| private void setKeyValueEditBoxes() { |
| final List<String> existingValueKeys = getExistingValueKeys(myDefaultPropertyValue); |
| |
| if (existingValueKeys.isEmpty()) { |
| getKeyTextField().setText(suggestPropertyKey(myDefaultPropertyValue)); |
| } |
| else { |
| for (String key : existingValueKeys) { |
| myKey.addItem(key); |
| } |
| myKey.setSelectedItem(existingValueKeys.get(0)); |
| } |
| |
| |
| myValue.setText(myDefaultPropertyValue); |
| } |
| |
| protected void somethingChanged() { |
| setOKActionEnabled(!StringUtil.isEmptyOrSpaces(getKey())); |
| } |
| |
| private void populatePropertiesFiles() { |
| List<String> paths = suggestPropertiesFiles(); |
| final String lastUrl = suggestSelectedFileUrl(paths); |
| final String lastPath = lastUrl == null ? null : FileUtil.toSystemDependentName(VfsUtil.urlToPath(lastUrl)); |
| Collections.sort(paths, new Comparator<String>() { |
| @Override |
| public int compare(final String path1, final String path2) { |
| if (lastPath != null && lastPath.equals(path1)) return -1; |
| if (lastPath != null && lastPath.equals(path2)) return 1; |
| int r = LastSelectedPropertiesFileStore.getUseCount(path2) - LastSelectedPropertiesFileStore.getUseCount(path1); |
| return r == 0 ? path1.compareTo(path2) : r; |
| } |
| }); |
| myPropertiesFile.setHistory(paths); |
| if (lastPath != null) { |
| myPropertiesFile.setSelectedItem(lastPath); |
| } |
| if (myPropertiesFile.getSelectedIndex() == -1 && !paths.isEmpty()) { |
| myPropertiesFile.setText(paths.get(0)); |
| } |
| } |
| |
| private String suggestSelectedFileUrl(List<String> paths) { |
| if (myDefaultPropertyValue != null) { |
| for (String path : paths) { |
| VirtualFile file = LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(path)); |
| if (file == null) continue; |
| PsiFile psiFile = myContext.getManager().findFile(file); |
| if (!(psiFile instanceof PropertiesFile)) continue; |
| for (IProperty property : ((PropertiesFile)psiFile).getProperties()) { |
| if (property.getValue().equals(myDefaultPropertyValue)) return path; |
| } |
| } |
| } |
| return LastSelectedPropertiesFileStore.getInstance().suggestLastSelectedPropertiesFileUrl(myContext); |
| } |
| |
| private void saveLastSelectedFile() { |
| PropertiesFile propertiesFile = getPropertiesFile(); |
| if (propertiesFile != null) { |
| LastSelectedPropertiesFileStore.getInstance().saveLastSelectedPropertiesFile(myContext, propertiesFile); |
| } |
| } |
| |
| protected List<String> suggestPropertiesFiles() { |
| if (myCustomization.propertiesFiles != null && !myCustomization.propertiesFiles.isEmpty()) { |
| ArrayList<String> list = new ArrayList<String>(); |
| for (PropertiesFile propertiesFile : myCustomization.propertiesFiles) { |
| final VirtualFile virtualFile = propertiesFile.getVirtualFile(); |
| if (virtualFile != null) { |
| list.add(virtualFile.getPath()); |
| } |
| } |
| return list; |
| } |
| return defaultSuggestPropertiesFiles(); |
| } |
| |
| protected List<String> defaultSuggestPropertiesFiles() { |
| return I18nUtil.defaultGetPropertyFiles(myProject); |
| } |
| |
| protected PropertiesFile getPropertiesFile() { |
| String path = FileUtil.toSystemIndependentName(myPropertiesFile.getText()); |
| VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(path); |
| if (virtualFile != null) { |
| PsiFile psiFile = PsiManager.getInstance(myProject).findFile(virtualFile); |
| if (psiFile instanceof PropertiesFile) return (PropertiesFile)psiFile; |
| } |
| return null; |
| } |
| |
| private boolean createPropertiesFileIfNotExists() { |
| if (getPropertiesFile() != null) return true; |
| final String path = FileUtil.toSystemIndependentName(myPropertiesFile.getText()); |
| if (StringUtil.isEmptyOrSpaces(path)) { |
| String message = CodeInsightBundle.message("i18nize.empty.file.path", myPropertiesFile.getText()); |
| Messages.showErrorDialog(myProject, message, CodeInsightBundle.message("i18nize.error.creating.properties.file")); |
| myPropertiesFile.requestFocusInWindow(); |
| return false; |
| } |
| FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(path); |
| if (fileType != StdFileTypes.PROPERTIES) { |
| String message = CodeInsightBundle.message("i18nize.cant.create.properties.file.because.its.name.is.associated", |
| myPropertiesFile.getText(), fileType.getDescription()); |
| Messages.showErrorDialog(myProject, message, CodeInsightBundle.message("i18nize.error.creating.properties.file")); |
| myPropertiesFile.requestFocusInWindow(); |
| return false; |
| } |
| |
| final VirtualFile virtualFile; |
| try { |
| final File file = new File(path).getCanonicalFile(); |
| FileUtil.createParentDirs(file); |
| virtualFile = ApplicationManager.getApplication().runWriteAction(new ThrowableComputable<VirtualFile, IOException>() { |
| @Override |
| public VirtualFile compute() throws IOException { |
| VirtualFile dir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file.getParentFile()); |
| if (dir == null) { |
| throw new IOException("Error creating directory structure for file '" + path + "'"); |
| } |
| return dir.createChildData(this, file.getName()); |
| } |
| }); |
| } |
| catch (IOException e) { |
| Messages.showErrorDialog(myProject, e.getLocalizedMessage(), CodeInsightBundle.message("i18nize.error.creating.properties.file")); |
| return false; |
| } |
| |
| PsiFile psiFile = PsiManager.getInstance(myProject).findFile(virtualFile); |
| return psiFile instanceof PropertiesFile; |
| } |
| |
| @Override |
| protected JComponent createCenterPanel() { |
| return myPanel; |
| } |
| |
| @Override |
| public JComponent getPreferredFocusedComponent() { |
| return myCustomization.focusValueComponent ? myValue:myKey; |
| } |
| |
| @Override |
| public void dispose() { |
| saveLastSelectedFile(); |
| super.dispose(); |
| } |
| |
| @Override |
| protected void doOKAction() { |
| if (!createPropertiesFileIfNotExists()) return; |
| Collection<PropertiesFile> propertiesFiles = getAllPropertiesFiles(); |
| for (PropertiesFile propertiesFile : propertiesFiles) { |
| IProperty existingProperty = propertiesFile.findPropertyByKey(getKey()); |
| final String propValue = myValue.getText(); |
| if (existingProperty != null && !Comparing.strEqual(existingProperty.getValue(), propValue)) { |
| Messages.showErrorDialog(myProject, CodeInsightBundle.message("i18nize.dialog.error.property.already.defined.message", getKey(), |
| propertiesFile.getName()), |
| CodeInsightBundle.message("i18nize.dialog.error.property.already.defined.title")); |
| return; |
| } |
| } |
| |
| super.doOKAction(); |
| } |
| |
| @Override |
| @NotNull |
| protected Action[] createActions() { |
| return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; |
| } |
| |
| @Override |
| public void doHelpAction() { |
| HelpManager.getInstance().invokeHelp("editing.propertyFile.i18nInspection"); |
| } |
| |
| |
| public JComponent getValueComponent() { |
| return myValue; |
| } |
| |
| @Override |
| public String getValue() { |
| return myValue.getText(); |
| } |
| |
| @Override |
| public String getKey() { |
| return getKeyTextField().getText(); |
| } |
| |
| @Override |
| public boolean hasValidData() { |
| assert !ApplicationManager.getApplication().isUnitTestMode(); |
| show(); |
| |
| return getExitCode() == OK_EXIT_CODE; |
| } |
| |
| private boolean isUseResourceBundle() { |
| return myUseResourceBundle.isEnabled() && myUseResourceBundle.isSelected(); |
| } |
| |
| @Override |
| protected String getDimensionServiceKey() { |
| return "#com.intellij.codeInsight.i18n.I18nizeQuickFixDialog"; |
| } |
| |
| @Override |
| public Collection<PropertiesFile> getAllPropertiesFiles() { |
| PropertiesFile propertiesFile = getPropertiesFile(); |
| if (propertiesFile == null) return Collections.emptySet(); |
| Collection<PropertiesFile> propertiesFiles; |
| if (isUseResourceBundle()) { |
| propertiesFiles = propertiesFile.getResourceBundle().getPropertiesFiles(); |
| } |
| else { |
| propertiesFiles = Collections.singleton(propertiesFile); |
| } |
| return propertiesFiles; |
| } |
| } |