| /* |
| * 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 Alexey |
| */ |
| package com.intellij.lang.properties.editor; |
| |
| import com.intellij.codeHighlighting.BackgroundEditorHighlighter; |
| import com.intellij.ide.FileEditorProvider; |
| import com.intellij.ide.SelectInContext; |
| import com.intellij.ide.structureView.StructureViewBuilder; |
| import com.intellij.ide.structureView.newStructureView.StructureViewComponent; |
| import com.intellij.ide.util.treeView.AbstractTreeNode; |
| import com.intellij.ide.util.treeView.AbstractTreeUi; |
| import com.intellij.ide.util.treeView.smartTree.TreeElement; |
| import com.intellij.lang.properties.IProperty; |
| import com.intellij.lang.properties.PropertiesImplUtil; |
| import com.intellij.lang.properties.ResourceBundle; |
| import com.intellij.lang.properties.psi.PropertiesFile; |
| import com.intellij.lang.properties.psi.PropertiesResourceBundleUtil; |
| import com.intellij.openapi.actionSystem.DataProvider; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.command.WriteCommandAction; |
| import com.intellij.openapi.command.undo.UndoConstants; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.*; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.colors.EditorColorsScheme; |
| import com.intellij.openapi.editor.ex.EditorEx; |
| import com.intellij.openapi.editor.ex.FocusChangeListener; |
| import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter; |
| import com.intellij.openapi.fileEditor.*; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.UserDataHolderBase; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.*; |
| import com.intellij.psi.*; |
| import com.intellij.ui.IdeBorderFactory; |
| import com.intellij.ui.JBColor; |
| import com.intellij.ui.JBSplitter; |
| import com.intellij.ui.components.JBScrollPane; |
| import com.intellij.util.Alarm; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.NullableFunction; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.ContainerUtilRt; |
| import com.intellij.util.containers.Stack; |
| import com.intellij.util.ui.UIUtil; |
| import gnu.trove.THashMap; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.border.TitledBorder; |
| import javax.swing.event.TreeSelectionEvent; |
| import javax.swing.event.TreeSelectionListener; |
| import javax.swing.tree.DefaultMutableTreeNode; |
| import javax.swing.tree.TreePath; |
| import java.awt.*; |
| import java.beans.PropertyChangeListener; |
| import java.util.*; |
| import java.util.List; |
| |
| public class ResourceBundleEditor extends UserDataHolderBase implements FileEditor { |
| private static final Logger LOG = |
| Logger.getInstance("#com.intellij.lang.properties.editor.ResourceBundleEditor"); |
| @NonNls private static final String VALUES = "values"; |
| @NonNls private static final String NO_PROPERTY_SELECTED = "noPropertySelected"; |
| |
| private final StructureViewComponent myStructureViewComponent; |
| private final Map<PropertiesFile, Editor> myEditors; |
| private final ResourceBundle myResourceBundle; |
| private final Map<PropertiesFile, JPanel> myTitledPanels; |
| private final JComponent myNoPropertySelectedPanel = new NoPropertySelectedPanel().getComponent(); |
| private final Project myProject; |
| private final DataProviderPanel myDataProviderPanel; |
| // user pressed backslash in the corresponding editor. |
| // we cannot store it back to properties file right now, so just append the backslash to the editor and wait for the subsequent chars |
| private final Set<PropertiesFile> myBackSlashPressed = new THashSet<PropertiesFile>(); |
| private final Alarm mySelectionChangeAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); |
| |
| private JPanel myValuesPanel; |
| private JPanel myStructureViewPanel; |
| private boolean myDisposed; |
| private VirtualFileListener myVfsListener; |
| private Editor mySelectedEditor; |
| |
| public ResourceBundleEditor(@NotNull ResourceBundle resourceBundle) { |
| myProject = resourceBundle.getProject(); |
| |
| final JPanel splitPanel = new JPanel(); |
| myValuesPanel = new JPanel(); |
| myStructureViewPanel = new JPanel(); |
| JBSplitter splitter = new JBSplitter(false); |
| splitter.setFirstComponent(myStructureViewPanel); |
| splitter.setSecondComponent(myValuesPanel); |
| splitter.setShowDividerControls(true); |
| splitter.setHonorComponentsMinimumSize(true); |
| splitter.setAndLoadSplitterProportionKey(getClass() + ".splitter"); |
| splitPanel.setLayout(new BorderLayout()); |
| splitPanel.add(splitter, BorderLayout.CENTER); |
| |
| myResourceBundle = resourceBundle; |
| myStructureViewComponent = new ResourceBundleStructureViewComponent(myResourceBundle, this); |
| myStructureViewPanel.setLayout(new BorderLayout()); |
| myStructureViewPanel.add(myStructureViewComponent, BorderLayout.CENTER); |
| |
| myStructureViewComponent.getTree().getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { |
| private IProperty selectedProperty; |
| private PropertiesFile selectedPropertiesFile; |
| |
| @Override |
| public void valueChanged(TreeSelectionEvent e) { |
| // filter out temp unselect/select events |
| if (Comparing.equal(e.getNewLeadSelectionPath(), e.getOldLeadSelectionPath()) || getSelectedProperty() == null) return; |
| if (!arePropertiesEquivalent(selectedProperty, getSelectedProperty()) || |
| !Comparing.equal(selectedPropertiesFile, getSelectedPropertiesFile())) { |
| |
| if (e.getOldLeadSelectionPath() != null) { |
| for (Map.Entry<PropertiesFile, Editor> entry : myEditors.entrySet()) { |
| if (entry.getValue() == mySelectedEditor) { |
| writeEditorPropertyValue(mySelectedEditor, entry.getKey(), selectedProperty.getName()); |
| break; |
| } |
| } |
| } |
| |
| selectedProperty = getSelectedProperty(); |
| selectedPropertiesFile = getSelectedPropertiesFile(); |
| selectionChanged(); |
| } |
| } |
| |
| private boolean arePropertiesEquivalent(@Nullable IProperty p1, @Nullable IProperty p2) { |
| if (p1 == p2) { |
| return true; |
| } |
| if (p1 == null || p2 == null) { |
| return false; |
| } |
| return p1.getPsiElement().isEquivalentTo(p2.getPsiElement()); |
| } |
| }); |
| installPropertiesChangeListeners(); |
| |
| myEditors = new THashMap<PropertiesFile, Editor>(); |
| myTitledPanels = new THashMap<PropertiesFile, JPanel>(); |
| recreateEditorsPanel(); |
| |
| TreeElement[] children = myStructureViewComponent.getTreeModel().getRoot().getChildren(); |
| if (children.length != 0) { |
| TreeElement child = children[0]; |
| String propName = ((ResourceBundlePropertyStructureViewElement)child).getProperty().getUnescapedKey(); |
| setState(new ResourceBundleEditorState(propName)); |
| } |
| myDataProviderPanel = new DataProviderPanel(splitPanel); |
| |
| myProject.getMessageBus().connect(myProject).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { |
| @Override |
| public void selectionChanged(@NotNull FileEditorManagerEvent event) { |
| onSelectionChanged(event); |
| } |
| }); |
| } |
| |
| public ResourceBundle getResourceBundle() { |
| return myResourceBundle; |
| } |
| |
| private void onSelectionChanged(@NotNull FileEditorManagerEvent event) { |
| // Ignore events which don't target current editor. |
| FileEditor oldEditor = event.getOldEditor(); |
| FileEditor newEditor = event.getNewEditor(); |
| if (oldEditor != this && newEditor != this) { |
| return; |
| } |
| |
| // We want to sync selected property key on selection change. |
| if (newEditor == this) { |
| if (oldEditor instanceof TextEditor) { |
| setStructureViewSelectionFromPropertiesFile(((TextEditor)oldEditor).getEditor()); |
| } |
| } |
| else if (newEditor instanceof TextEditor) { |
| setPropertiesFileSelectionFromStructureView(((TextEditor)newEditor).getEditor()); |
| } |
| } |
| |
| private void setStructureViewSelectionFromPropertiesFile(@NotNull Editor propertiesFileEditor) { |
| int line = propertiesFileEditor.getCaretModel().getLogicalPosition().line; |
| Document document = propertiesFileEditor.getDocument(); |
| if (line >= document.getLineCount()) { |
| return; |
| } |
| final String propertyName = getPropertyName(document, line); |
| if (propertyName == null) { |
| return; |
| } |
| setStructureViewSelection(propertyName); |
| } |
| |
| private void setStructureViewSelection(@NotNull final String propertyName) { |
| if (myStructureViewComponent.isDisposed()) { |
| return; |
| } |
| JTree tree = myStructureViewComponent.getTree(); |
| if (tree == null) { |
| return; |
| } |
| |
| Object root = tree.getModel().getRoot(); |
| if (AbstractTreeUi.isLoadingChildrenFor(root)) { |
| mySelectionChangeAlarm.cancelAllRequests(); |
| mySelectionChangeAlarm.addRequest(new Runnable() { |
| @Override |
| public void run() { |
| mySelectionChangeAlarm.cancelAllRequests(); |
| setStructureViewSelection(propertyName); |
| } |
| }, 500); |
| return; |
| } |
| |
| Stack<DefaultMutableTreeNode> toCheck = ContainerUtilRt.newStack(); |
| toCheck.push((DefaultMutableTreeNode)root); |
| DefaultMutableTreeNode nodeToSelect = null; |
| while (!toCheck.isEmpty()) { |
| DefaultMutableTreeNode node = toCheck.pop(); |
| final ResourceBundleEditorViewElement element = getSelectedElement(node); |
| String value = element instanceof ResourceBundlePropertyStructureViewElement |
| ? ((ResourceBundlePropertyStructureViewElement)element).getProperty().getUnescapedKey() |
| : null; |
| if (propertyName.equals(value)) { |
| nodeToSelect = node; |
| break; |
| } |
| else { |
| for (int i = 0; i < node.getChildCount(); i++) { |
| toCheck.push((DefaultMutableTreeNode)node.getChildAt(i)); |
| } |
| } |
| } |
| if (nodeToSelect != null) { |
| TreePath path = new TreePath(nodeToSelect.getPath()); |
| tree.setSelectionPath(path); |
| tree.scrollPathToVisible(path); |
| } |
| } |
| |
| @Nullable |
| private static String getPropertyName(@NotNull Document document, int line) { |
| int startOffset = document.getLineStartOffset(line); |
| int endOffset = StringUtil.indexOf(document.getCharsSequence(), '=', startOffset, document.getLineEndOffset(line)); |
| if (endOffset <= startOffset) { |
| return null; |
| } |
| String propertyName = document.getCharsSequence().subSequence(startOffset, endOffset).toString().trim(); |
| return propertyName.isEmpty() ? null : propertyName; |
| } |
| |
| private void setPropertiesFileSelectionFromStructureView(@NotNull Editor propertiesFileEditor) { |
| String selectedPropertyName = getSelectedPropertyName(); |
| if (selectedPropertyName == null) { |
| return; |
| } |
| |
| Document document = propertiesFileEditor.getDocument(); |
| for (int i = 0; i < document.getLineCount(); i++) { |
| String propertyName = getPropertyName(document, i); |
| if (selectedPropertyName.equals(propertyName)) { |
| propertiesFileEditor.getCaretModel().moveToLogicalPosition(new LogicalPosition(i, 0)); |
| return; |
| } |
| } |
| } |
| |
| @Nullable |
| private static ResourceBundleEditorViewElement getSelectedElement(@NotNull DefaultMutableTreeNode node) { |
| Object userObject = node.getUserObject(); |
| if (!(userObject instanceof AbstractTreeNode)) return null; |
| Object value = ((AbstractTreeNode)userObject).getValue(); |
| return value instanceof ResourceBundleEditorViewElement ? (ResourceBundleEditorViewElement) value : null; |
| } |
| |
| private void writeEditorPropertyValue(final Editor editor, final PropertiesFile propertiesFile, final @Nullable String propertyName) { |
| final String currentValue = editor.getDocument().getText(); |
| final String currentSelectedProperty = propertyName == null ? getSelectedPropertyName() : propertyName; |
| if (currentSelectedProperty == null) { |
| return; |
| } |
| |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| WriteCommandAction.runWriteCommandAction(myProject, new Runnable() { |
| @Override |
| public void run() { |
| final IProperty property = propertiesFile.findPropertyByKey(currentSelectedProperty); |
| try { |
| if (property == null) { |
| propertiesFile.addProperty(currentSelectedProperty, currentValue); |
| } |
| else { |
| property.setValue(currentValue); |
| } |
| } |
| catch (final IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| }); |
| |
| } |
| }); |
| } |
| |
| private void recreateEditorsPanel() { |
| myValuesPanel.removeAll(); |
| myValuesPanel.setLayout(new CardLayout()); |
| |
| if (!myProject.isOpen()) return; |
| JPanel valuesPanelComponent = new MyJPanel(new GridBagLayout()); |
| myValuesPanel.add(new JBScrollPane(valuesPanelComponent){ |
| @Override |
| public void updateUI() { |
| super.updateUI(); |
| getViewport().setBackground(UIUtil.getPanelBackground()); |
| } |
| }, VALUES); |
| myValuesPanel.add(myNoPropertySelectedPanel, NO_PROPERTY_SELECTED); |
| |
| List<PropertiesFile> propertiesFiles = myResourceBundle.getPropertiesFiles(); |
| |
| GridBagConstraints gc = new GridBagConstraints(0, 0, 0, 0, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, |
| new Insets(5, 5, 5, 5), 0, 0); |
| releaseAllEditors(); |
| myTitledPanels.clear(); |
| int y = 0; |
| Editor previousEditor = null; |
| Editor firstEditor = null; |
| for (final PropertiesFile propertiesFile : propertiesFiles) { |
| final Editor editor = createEditor(); |
| final Editor oldEditor = myEditors.put(propertiesFile, editor); |
| if (firstEditor == null) { |
| firstEditor = editor; |
| } |
| if (previousEditor != null) { |
| editor.putUserData(ChooseSubsequentPropertyValueEditorAction.PREV_EDITOR_KEY, previousEditor); |
| previousEditor.putUserData(ChooseSubsequentPropertyValueEditorAction.NEXT_EDITOR_KEY, editor); |
| } |
| previousEditor = editor; |
| if (oldEditor != null) { |
| EditorFactory.getInstance().releaseEditor(oldEditor); |
| } |
| ((EditorEx) editor).addFocusListener(new FocusChangeListener() { |
| @Override |
| public void focusGained(final Editor editor) { |
| mySelectedEditor = editor; |
| } |
| |
| @Override |
| public void focusLost(final Editor eventEditor) { |
| writeEditorPropertyValue(editor, propertiesFile, null); |
| } |
| }); |
| editor.getDocument().putUserData(UndoConstants.DONT_RECORD_UNDO, Boolean.TRUE); |
| gc.gridx = 0; |
| gc.gridy = y++; |
| gc.gridheight = 1; |
| gc.gridwidth = GridBagConstraints.REMAINDER; |
| gc.weightx = 1; |
| gc.weighty = 1; |
| gc.anchor = GridBagConstraints.CENTER; |
| |
| Locale locale = propertiesFile.getLocale(); |
| List<String> names = new ArrayList<String>(); |
| if (!Comparing.strEqual(locale.getDisplayLanguage(), null)) { |
| names.add(locale.getDisplayLanguage()); |
| } |
| if (!Comparing.strEqual(locale.getDisplayCountry(), null)) { |
| names.add(locale.getDisplayCountry()); |
| } |
| if (!Comparing.strEqual(locale.getDisplayVariant(), null)) { |
| names.add(locale.getDisplayVariant()); |
| } |
| |
| String title = propertiesFile.getName(); |
| if (!names.isEmpty()) { |
| title += " ("+StringUtil.join(names, "/")+")"; |
| } |
| JComponent comp = new JPanel(new BorderLayout()) { |
| @Override |
| public Dimension getPreferredSize() { |
| Insets insets = getBorder().getBorderInsets(this); |
| return new Dimension(100,editor.getLineHeight()*4+ insets.top + insets.bottom); |
| } |
| }; |
| comp.add(editor.getComponent(), BorderLayout.CENTER); |
| comp.setBorder(IdeBorderFactory.createTitledBorder(title, true)); |
| myTitledPanels.put(propertiesFile, (JPanel)comp); |
| |
| valuesPanelComponent.add(comp, gc); |
| } |
| if (previousEditor != null) { |
| previousEditor.putUserData(ChooseSubsequentPropertyValueEditorAction.NEXT_EDITOR_KEY, firstEditor); |
| firstEditor.putUserData(ChooseSubsequentPropertyValueEditorAction.PREV_EDITOR_KEY, previousEditor); |
| } |
| |
| gc.gridx = 0; |
| gc.gridy = y; |
| gc.gridheight = GridBagConstraints.REMAINDER; |
| gc.gridwidth = GridBagConstraints.REMAINDER; |
| gc.weightx = 10; |
| gc.weighty = 1; |
| |
| valuesPanelComponent.add(new JPanel(), gc); |
| selectionChanged(); |
| myValuesPanel.repaint(); |
| UIUtil.invokeAndWaitIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| updateEditorsFromProperties(); |
| } |
| }); |
| } |
| |
| @NotNull |
| public static String getPropertyEditorValue(@Nullable final IProperty property) { |
| if (property == null) { |
| return ""; |
| } |
| else { |
| String rawValue = property.getValue(); |
| return rawValue == null ? "" : PropertiesResourceBundleUtil.fromPropertyValueToValueEditor(rawValue); |
| } |
| } |
| |
| private void updateEditorsFromProperties() { |
| String propertyName = getSelectedPropertyName(); |
| ((CardLayout)myValuesPanel.getLayout()).show(myValuesPanel, propertyName == null ? NO_PROPERTY_SELECTED : VALUES); |
| if (propertyName == null) return; |
| |
| for (final PropertiesFile propertiesFile : myResourceBundle.getPropertiesFiles(myProject)) { |
| final EditorEx editor = (EditorEx)myEditors.get(propertiesFile); |
| if (editor == null) continue; |
| final IProperty property = propertiesFile.findPropertyByKey(propertyName); |
| final Document document = editor.getDocument(); |
| CommandProcessor.getInstance().executeCommand(null, new Runnable() { |
| @Override |
| public void run() { |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| updateDocumentFromPropertyValue(getPropertyEditorValue(property), document, propertiesFile); |
| } |
| }); |
| } |
| }, "", this); |
| |
| JPanel titledPanel = myTitledPanels.get(propertiesFile); |
| ((TitledBorder)titledPanel.getBorder()).setTitleColor(property == null ? JBColor.RED : UIUtil.getLabelTextForeground()); |
| titledPanel.repaint(); |
| } |
| } |
| |
| private void installPropertiesChangeListeners() { |
| final VirtualFileManager virtualFileManager = VirtualFileManager.getInstance(); |
| if (myVfsListener != null) { |
| assert false; |
| virtualFileManager.removeVirtualFileListener(myVfsListener); |
| } |
| myVfsListener = new VirtualFileAdapter() { |
| @Override |
| public void fileCreated(@NotNull VirtualFileEvent event) { |
| if (PropertiesImplUtil.isPropertiesFile(event.getFile(), myProject)) { |
| recreateEditorsPanel(); |
| } |
| } |
| |
| @Override |
| public void fileDeleted(@NotNull VirtualFileEvent event) { |
| for (PropertiesFile file : myEditors.keySet()) { |
| if (Comparing.equal(file.getVirtualFile(), event.getFile())) { |
| recreateEditorsPanel(); |
| return; |
| } |
| } |
| } |
| |
| @Override |
| public void propertyChanged(@NotNull VirtualFilePropertyEvent event) { |
| if (PropertiesImplUtil.isPropertiesFile(event.getFile(), myProject)) { |
| if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) { |
| recreateEditorsPanel(); |
| } |
| else { |
| updateEditorsFromProperties(); |
| } |
| } |
| } |
| }; |
| |
| virtualFileManager.addVirtualFileListener(myVfsListener, this); |
| PsiTreeChangeAdapter psiTreeChangeAdapter = new PsiTreeChangeAdapter() { |
| @Override |
| public void childAdded(@NotNull PsiTreeChangeEvent event) { |
| childrenChanged(event); |
| } |
| |
| @Override |
| public void childRemoved(@NotNull PsiTreeChangeEvent event) { |
| childrenChanged(event); |
| } |
| |
| @Override |
| public void childReplaced(@NotNull PsiTreeChangeEvent event) { |
| childrenChanged(event); |
| } |
| |
| @Override |
| public void childMoved(@NotNull PsiTreeChangeEvent event) { |
| childrenChanged(event); |
| } |
| |
| @Override |
| public void childrenChanged(@NotNull PsiTreeChangeEvent event) { |
| final PsiFile file = event.getFile(); |
| PropertiesFile propertiesFile = PropertiesImplUtil.getPropertiesFile(file); |
| if (propertiesFile == null) return; |
| if (!propertiesFile.getResourceBundle().equals(myResourceBundle)) return; |
| updateEditorsFromProperties(); |
| } |
| }; |
| PsiManager.getInstance(myProject).addPsiTreeChangeListener(psiTreeChangeAdapter, this); |
| } |
| private void selectionChanged() { |
| myBackSlashPressed.clear(); |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| updateEditorsFromProperties(); |
| } |
| }); |
| } |
| |
| private void updateDocumentFromPropertyValue(final String value, |
| final Document document, |
| final PropertiesFile propertiesFile) { |
| @NonNls String text = value; |
| if (myBackSlashPressed.contains(propertiesFile)) { |
| text += "\\"; |
| } |
| document.replaceString(0, document.getTextLength(), text); |
| } |
| |
| @NotNull |
| private Collection<DefaultMutableTreeNode> getSelectedNodes() { |
| if (!isValid()) { |
| return Collections.emptyList(); |
| } |
| JTree tree = myStructureViewComponent.getTree(); |
| if (tree == null) return Collections.emptyList(); |
| TreePath[] selected = tree.getSelectionModel().getSelectionPaths(); |
| if (selected == null || selected.length == 0) return Collections.emptyList(); |
| return ContainerUtil.map(selected, new Function<TreePath, DefaultMutableTreeNode>() { |
| @Override |
| public DefaultMutableTreeNode fun(TreePath treePath) { |
| return (DefaultMutableTreeNode)treePath.getLastPathComponent(); |
| } |
| }); |
| } |
| |
| @Nullable |
| private String getSelectedPropertyName() { |
| final IProperty selectedProperty = getSelectedProperty(); |
| return selectedProperty == null ? null : selectedProperty.getName(); |
| } |
| |
| @Nullable |
| private IProperty getSelectedProperty() { |
| final Collection<DefaultMutableTreeNode> selectedNode = getSelectedNodes(); |
| if (selectedNode.isEmpty()) { |
| return null; |
| } |
| final ResourceBundleEditorViewElement element = getSelectedElement(ContainerUtil.getFirstItem(selectedNode)); |
| return element instanceof ResourceBundlePropertyStructureViewElement ? ((ResourceBundlePropertyStructureViewElement)element).getProperty() |
| : null; |
| } |
| |
| @NotNull |
| public Collection<ResourceBundleEditorViewElement> getSelectedElements() { |
| final Collection<DefaultMutableTreeNode> selectedNodes = getSelectedNodes(); |
| return ContainerUtil.mapNotNull(selectedNodes, new NullableFunction<DefaultMutableTreeNode, ResourceBundleEditorViewElement>() { |
| @Nullable |
| @Override |
| public ResourceBundleEditorViewElement fun(DefaultMutableTreeNode selectedNode) { |
| Object userObject = selectedNode.getUserObject(); |
| if (!(userObject instanceof AbstractTreeNode)) return null; |
| Object value = ((AbstractTreeNode)userObject).getValue(); |
| return value instanceof ResourceBundleEditorViewElement ? (ResourceBundleEditorViewElement) value : null; |
| } |
| }); |
| } |
| |
| @Nullable |
| public ResourceBundleEditorViewElement getSelectedElementIfOnlyOne() { |
| final Collection<ResourceBundleEditorViewElement> selectedElements = getSelectedElements(); |
| return selectedElements.size() == 1 ? ContainerUtil.getFirstItem(selectedElements) : null; |
| } |
| |
| @Override |
| @NotNull |
| public JComponent getComponent() { |
| return myDataProviderPanel; |
| } |
| |
| private Object getData(final String dataId) { |
| if (SelectInContext.DATA_KEY.is(dataId)) { |
| return new SelectInContext(){ |
| @Override |
| @NotNull |
| public Project getProject() { |
| return myProject; |
| } |
| |
| @Override |
| @NotNull |
| public VirtualFile getVirtualFile() { |
| PropertiesFile selectedFile = getSelectedPropertiesFile(); |
| |
| VirtualFile virtualFile = selectedFile == null ? null : selectedFile.getVirtualFile(); |
| assert virtualFile != null; |
| return virtualFile; |
| } |
| |
| @Override |
| public Object getSelectorInFile() { |
| return getSelectedPropertiesFile(); |
| } |
| |
| @Override |
| public FileEditorProvider getFileEditorProvider() { |
| final PropertiesFile selectedPropertiesFile = getSelectedPropertiesFile(); |
| if (selectedPropertiesFile == null) return null; |
| return new FileEditorProvider() { |
| @Override |
| public FileEditor openFileEditor() { |
| final VirtualFile file = selectedPropertiesFile.getVirtualFile(); |
| if (file == null) { |
| return null; |
| } |
| return FileEditorManager.getInstance(getProject()).openFile(file, false)[0]; |
| |
| } |
| }; |
| } |
| }; |
| } |
| return null; |
| } |
| |
| private PropertiesFile getSelectedPropertiesFile() { |
| if (mySelectedEditor == null) return null; |
| PropertiesFile selectedFile = null; |
| for (Map.Entry<PropertiesFile, Editor> entry : myEditors.entrySet()) { |
| Editor editor = entry.getValue(); |
| if (editor == mySelectedEditor) { |
| selectedFile = entry.getKey(); |
| break; |
| } |
| } |
| return selectedFile; |
| } |
| |
| @Override |
| public JComponent getPreferredFocusedComponent() { |
| return myStructureViewPanel; |
| } |
| |
| @Override |
| @NotNull |
| public String getName() { |
| return "Resource Bundle"; |
| } |
| |
| @Override |
| @NotNull |
| public ResourceBundleEditorState getState(@NotNull FileEditorStateLevel level) { |
| return new ResourceBundleEditorState(getSelectedPropertyName()); |
| } |
| |
| @Override |
| public void setState(@NotNull FileEditorState state) { |
| ResourceBundleEditorState myState = (ResourceBundleEditorState)state; |
| String propertyName = myState.myPropertyName; |
| if (propertyName != null) { |
| myStructureViewComponent.select(propertyName, true); |
| selectionChanged(); |
| } |
| } |
| |
| @Override |
| public boolean isModified() { |
| return false; |
| } |
| |
| @Override |
| public boolean isValid() { |
| return !myDisposed && !myProject.isDisposed(); |
| } |
| |
| @Override |
| public void selectNotify() { |
| |
| } |
| |
| @Override |
| public void deselectNotify() { |
| |
| } |
| |
| @Override |
| public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) { |
| |
| } |
| |
| @Override |
| public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) { |
| |
| } |
| |
| @Override |
| public BackgroundEditorHighlighter getBackgroundHighlighter() { |
| return null; |
| } |
| |
| @Override |
| public FileEditorLocation getCurrentLocation() { |
| return null; |
| } |
| |
| @Override |
| public StructureViewBuilder getStructureViewBuilder() { |
| return null; |
| } |
| |
| @Override |
| public void dispose() { |
| if (mySelectedEditor != null) { |
| for (final Map.Entry<PropertiesFile, Editor> entry : myEditors.entrySet()) { |
| if (mySelectedEditor.equals(entry.getValue())) { |
| writeEditorPropertyValue(mySelectedEditor, entry.getKey(), null); |
| } |
| } |
| } |
| |
| VirtualFileManager.getInstance().removeVirtualFileListener(myVfsListener); |
| myDisposed = true; |
| Disposer.dispose(myStructureViewComponent); |
| releaseAllEditors(); |
| } |
| |
| private void releaseAllEditors() { |
| for (final Editor editor : myEditors.values()) { |
| if (!editor.isDisposed()) { |
| EditorFactory.getInstance().releaseEditor(editor); |
| } |
| } |
| myEditors.clear(); |
| } |
| |
| public static class ResourceBundleEditorState implements FileEditorState { |
| private final String myPropertyName; |
| |
| public ResourceBundleEditorState(String propertyName) { |
| myPropertyName = propertyName; |
| } |
| |
| @Override |
| public boolean canBeMergedWith(FileEditorState otherState, FileEditorStateLevel level) { |
| return false; |
| } |
| |
| public String getPropertyName() { |
| return myPropertyName; |
| } |
| } |
| |
| private static Editor createEditor() { |
| EditorFactory editorFactory = EditorFactory.getInstance(); |
| Document document = editorFactory.createDocument(""); |
| EditorEx editor = (EditorEx)editorFactory.createEditor(document); |
| reinitSettings(editor); |
| return editor; |
| } |
| |
| private static void reinitSettings(final EditorEx editor) { |
| EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); |
| editor.setColorsScheme(scheme); |
| EditorSettings settings = editor.getSettings(); |
| settings.setLineNumbersShown(false); |
| settings.setWhitespacesShown(false); |
| settings.setLineMarkerAreaShown(false); |
| settings.setIndentGuidesShown(false); |
| settings.setFoldingOutlineShown(false); |
| settings.setAdditionalColumnsCount(0); |
| settings.setAdditionalLinesCount(0); |
| settings.setRightMarginShown(true); |
| settings.setRightMargin(60); |
| settings.setVirtualSpace(false); |
| |
| editor.setHighlighter(new LexerEditorHighlighter(new PropertiesValueHighlighter(), scheme)); |
| editor.setVerticalScrollbarVisible(true); |
| } |
| |
| private class DataProviderPanel extends JPanel implements DataProvider { |
| private DataProviderPanel(final JPanel panel) { |
| super(new BorderLayout()); |
| add(panel, BorderLayout.CENTER); |
| } |
| |
| @Override |
| @Nullable |
| public Object getData(String dataId) { |
| return ResourceBundleEditor.this.getData(dataId); |
| } |
| } |
| |
| private class MyJPanel extends JPanel implements Scrollable{ |
| private MyJPanel(LayoutManager layout) { |
| super(layout); |
| } |
| |
| @Override |
| public Dimension getPreferredScrollableViewportSize() { |
| return getPreferredSize(); |
| } |
| |
| @Override |
| public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { |
| Editor editor = myEditors.values().iterator().next(); |
| return editor.getLineHeight()*4; |
| } |
| |
| @Override |
| public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { |
| return visibleRect.height; |
| } |
| |
| @Override |
| public boolean getScrollableTracksViewportWidth() { |
| return true; |
| } |
| |
| @Override |
| public boolean getScrollableTracksViewportHeight() { |
| return false; |
| } |
| } |
| |
| } |