/* * 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 com.intellij.codeInsight.template.impl; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.template.EverywhereContextType; import com.intellij.codeInsight.template.TemplateContextType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupAdapter; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.LightweightWindowEvent; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.ui.*; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.util.ui.GridBag; import com.intellij.util.ui.PlatformColors; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import com.intellij.util.ui.update.Activatable; import com.intellij.util.ui.update.UiNotifyConnector; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.*; import java.util.*; import java.util.List; public class LiveTemplateSettingsEditor extends JPanel { private final TemplateImpl myTemplate; private final JTextField myKeyField; private final JTextField myDescription; private final Editor myTemplateEditor; private JComboBox myExpandByCombo; private final String myDefaultShortcutItem; private JCheckBox myCbReformat; private JButton myEditVariablesButton; private static final String SPACE = CodeInsightBundle.message("template.shortcut.space"); private static final String TAB = CodeInsightBundle.message("template.shortcut.tab"); private static final String ENTER = CodeInsightBundle.message("template.shortcut.enter"); private final Map<TemplateOptionalProcessor, Boolean> myOptions; private final Map<TemplateContextType, Boolean> myContext; private JBPopup myContextPopup; private Dimension myLastSize; public LiveTemplateSettingsEditor(TemplateImpl template, final String defaultShortcut, Map<TemplateOptionalProcessor, Boolean> options, Map<TemplateContextType, Boolean> context, final Runnable nodeChanged, boolean allowNoContext) { super(new BorderLayout()); myOptions = options; myContext = context; myTemplate = template; myDefaultShortcutItem = CodeInsightBundle.message("dialog.edit.template.shortcut.default", defaultShortcut); myKeyField=new JTextField(); myDescription=new JTextField(); myTemplateEditor = TemplateEditorUtil.createEditor(false, myTemplate.getString(), context); myTemplate.setId(null); createComponents(allowNoContext); myKeyField.getDocument().addDocumentListener(new com.intellij.ui.DocumentAdapter() { @Override protected void textChanged(javax.swing.event.DocumentEvent e) { myTemplate.setKey(myKeyField.getText().trim()); nodeChanged.run(); } }); myDescription.getDocument().addDocumentListener(new com.intellij.ui.DocumentAdapter() { @Override protected void textChanged(javax.swing.event.DocumentEvent e) { myTemplate.setDescription(myDescription.getText().trim()); nodeChanged.run(); } }); new UiNotifyConnector(this, new Activatable.Adapter() { @Override public void hideNotify() { disposeContextPopup(); } }); } public TemplateImpl getTemplate() { return myTemplate; } public void dispose() { final Project project = myTemplateEditor.getProject(); if (project != null) { final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(myTemplateEditor.getDocument()); if (psiFile != null) { DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(psiFile, true); } } EditorFactory.getInstance().releaseEditor(myTemplateEditor); } private void createComponents(boolean allowNoContexts) { JPanel panel = new JPanel(new GridBagLayout()); GridBag gb = new GridBag().setDefaultInsets(4, 4, 4, 4).setDefaultWeightY(1).setDefaultFill(GridBagConstraints.BOTH); JPanel editorPanel = new JPanel(new BorderLayout(4, 4)); editorPanel.setPreferredSize(new Dimension(250, 100)); editorPanel.setMinimumSize(editorPanel.getPreferredSize()); editorPanel.add(myTemplateEditor.getComponent(), BorderLayout.CENTER); JLabel templateTextLabel = new JLabel(CodeInsightBundle.message("dialog.edit.template.template.text.title")); templateTextLabel.setLabelFor(myTemplateEditor.getContentComponent()); editorPanel.add(templateTextLabel, BorderLayout.NORTH); panel.add(editorPanel, gb.nextLine().next().weighty(1).weightx(1).coverColumn(2)); myEditVariablesButton = new JButton(CodeInsightBundle.message("dialog.edit.template.button.edit.variables")); myEditVariablesButton.setDefaultCapable(false); myEditVariablesButton.setMaximumSize(myEditVariablesButton.getPreferredSize()); panel.add(myEditVariablesButton, gb.next().weighty(0)); panel.add(createTemplateOptionsPanel(), gb.nextLine().next().next().coverColumn(2).weighty(1)); panel.add(createShortContextPanel(allowNoContexts), gb.nextLine().next().weighty(0).fillCellNone().anchor(GridBagConstraints.WEST)); myTemplateEditor.getDocument().addDocumentListener( new DocumentAdapter() { @Override public void documentChanged(DocumentEvent e) { validateEditVariablesButton(); myTemplate.setString(myTemplateEditor.getDocument().getText()); applyVariables(updateVariablesByTemplateText()); } } ); myEditVariablesButton.addActionListener( new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { editVariables(); } } ); add(createNorthPanel(), BorderLayout.NORTH); add(panel, BorderLayout.CENTER); } private void applyVariables(final List<Variable> variables) { myTemplate.removeAllParsed(); for (Variable variable : variables) { myTemplate.addVariable(variable.getName(), variable.getExpressionString(), variable.getDefaultValueString(), variable.isAlwaysStopAt()); } myTemplate.parseSegments(); } @Nullable private JComponent createNorthPanel() { JPanel panel = new JPanel(new GridBagLayout()); GridBag gb = new GridBag().setDefaultInsets(4, 4, 4, 4).setDefaultWeightY(1).setDefaultFill(GridBagConstraints.BOTH); JLabel keyPrompt = new JLabel(CodeInsightBundle.message("dialog.edit.template.label.abbreviation")); keyPrompt.setLabelFor(myKeyField); panel.add(keyPrompt, gb.nextLine().next()); panel.add(myKeyField, gb.next().weightx(1)); JLabel descriptionPrompt = new JLabel(CodeInsightBundle.message("dialog.edit.template.label.description")); descriptionPrompt.setLabelFor(myDescription); panel.add(descriptionPrompt, gb.next()); panel.add(myDescription, gb.next().weightx(3)); return panel; } private JPanel createTemplateOptionsPanel() { JPanel panel = new JPanel(); panel.setBorder(IdeBorderFactory.createTitledBorder(CodeInsightBundle.message("dialog.edit.template.options.title"), true)); panel.setLayout(new GridBagLayout()); GridBagConstraints gbConstraints = new GridBagConstraints(); gbConstraints.fill = GridBagConstraints.BOTH; gbConstraints.weighty = 0; gbConstraints.weightx = 0; gbConstraints.gridy = 0; JLabel expandWithLabel = new JLabel(CodeInsightBundle.message("dialog.edit.template.label.expand.with")); panel.add(expandWithLabel, gbConstraints); gbConstraints.gridx = 1; gbConstraints.insets = new Insets(0, 4, 0, 0); myExpandByCombo = new JComboBox(new Object[]{myDefaultShortcutItem, SPACE, TAB, ENTER}); myExpandByCombo.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { Object selectedItem = myExpandByCombo.getSelectedItem(); if(myDefaultShortcutItem.equals(selectedItem)) { myTemplate.setShortcutChar(TemplateSettings.DEFAULT_CHAR); } else if(TAB.equals(selectedItem)) { myTemplate.setShortcutChar(TemplateSettings.TAB_CHAR); } else if(ENTER.equals(selectedItem)) { myTemplate.setShortcutChar(TemplateSettings.ENTER_CHAR); } else { myTemplate.setShortcutChar(TemplateSettings.SPACE_CHAR); } } }); expandWithLabel.setLabelFor(myExpandByCombo); panel.add(myExpandByCombo, gbConstraints); gbConstraints.weightx = 1; gbConstraints.gridx = 2; panel.add(new JPanel(), gbConstraints); gbConstraints.gridx = 0; gbConstraints.gridy++; gbConstraints.gridwidth = 3; myCbReformat = new JCheckBox(CodeInsightBundle.message("dialog.edit.template.checkbox.reformat.according.to.style")); panel.add(myCbReformat, gbConstraints); for (final TemplateOptionalProcessor processor: myOptions.keySet()) { if (!processor.isVisible(myTemplate)) continue; gbConstraints.gridy++; final JCheckBox cb = new JCheckBox(processor.getOptionName()); panel.add(cb, gbConstraints); cb.setSelected(myOptions.get(processor).booleanValue()); cb.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { myOptions.put(processor, cb.isSelected()); } }); } gbConstraints.weighty = 1; gbConstraints.gridy++; panel.add(new JPanel(), gbConstraints); return panel; } private List<TemplateContextType> getApplicableContexts() { ArrayList<TemplateContextType> result = new ArrayList<TemplateContextType>(); for (TemplateContextType type : myContext.keySet()) { if (myContext.get(type).booleanValue()) { result.add(type); } } return result; } private JPanel createShortContextPanel(final boolean allowNoContexts) { JPanel panel = new JPanel(new BorderLayout()); final JLabel ctxLabel = new JLabel(); final JLabel change = new JLabel(); change.setForeground(PlatformColors.BLUE); change.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); panel.add(ctxLabel, BorderLayout.CENTER); panel.add(change, BorderLayout.EAST); final Runnable updateLabel = new Runnable() { @Override public void run() { StringBuilder sb = new StringBuilder(); String oldPrefix = ""; for (TemplateContextType type : getApplicableContexts()) { final TemplateContextType base = type.getBaseContextType(); String ownName = UIUtil.removeMnemonic(type.getPresentableName()); String prefix = ""; if (base != null && !(base instanceof EverywhereContextType)) { prefix = UIUtil.removeMnemonic(base.getPresentableName()) + ": "; ownName = StringUtil.decapitalize(ownName); } if (type instanceof EverywhereContextType) { ownName = "Other"; } if (sb.length() > 0) { sb.append(oldPrefix.equals(prefix) ? ", " : "; "); } if (!oldPrefix.equals(prefix)) { sb.append(prefix); oldPrefix = prefix; } sb.append(ownName); } final boolean noContexts = sb.length() == 0; ctxLabel.setText((noContexts ? "No applicable contexts" + (allowNoContexts ? "" : " yet") : "Applicable in " + sb.toString()) + ". "); ctxLabel.setForeground(noContexts ? allowNoContexts ? JBColor.GRAY : JBColor.RED : UIUtil.getLabelForeground()); change.setText(noContexts ? "Define" : "Change"); } }; new ClickListener() { @Override public boolean onClick(MouseEvent e, int clickCount) { if (disposeContextPopup()) return false; final JPanel content = createPopupContextPanel(updateLabel); Dimension prefSize = content.getPreferredSize(); if (myLastSize != null && (myLastSize.width > prefSize.width || myLastSize.height > prefSize.height)) { content.setPreferredSize(new Dimension(Math.max(prefSize.width, myLastSize.width), Math.max(prefSize.height, myLastSize.height))); } myContextPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(content, null).setResizable(true).createPopup(); myContextPopup.show(new RelativePoint(change, new Point(change.getWidth() , -content.getPreferredSize().height - 10))); myContextPopup.addListener(new JBPopupAdapter() { @Override public void onClosed(LightweightWindowEvent event) { myLastSize = content.getSize(); } }); return true; } }.installOn(change); updateLabel.run(); return panel; } private boolean disposeContextPopup() { if (myContextPopup != null && myContextPopup.isVisible()) { myContextPopup.cancel(); myContextPopup = null; return true; } return false; } private JPanel createPopupContextPanel(final Runnable onChange) { JPanel panel = new JPanel(new BorderLayout()); MultiMap<TemplateContextType, TemplateContextType> hierarchy = new MultiMap<TemplateContextType, TemplateContextType>() { @Override protected Map<TemplateContextType, Collection<TemplateContextType>> createMap() { return new LinkedHashMap<TemplateContextType, Collection<TemplateContextType>>(); } }; for (TemplateContextType type : myContext.keySet()) { hierarchy.putValue(type.getBaseContextType(), type); } final CheckedTreeNode root = new CheckedTreeNode(Pair.create(null, "Hi")); final CheckboxTree checkboxTree = new CheckboxTree(new CheckboxTree.CheckboxTreeCellRenderer() { @Override public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { final Object o = ((DefaultMutableTreeNode)value).getUserObject(); if (o instanceof Pair) { getTextRenderer().append((String)((Pair)o).second); } } }, root) { @Override protected void onNodeStateChanged(CheckedTreeNode node) { final TemplateContextType type = (TemplateContextType)((Pair)node.getUserObject()).first; if (type != null) { myContext.put(type, node.isChecked()); } myExpandByCombo.setEnabled(isExpandableFromEditor()); updateHighlighter(); onChange.run(); } }; for (TemplateContextType type : hierarchy.get(null)) { addContextNode(hierarchy, root, type); } ((DefaultTreeModel)checkboxTree.getModel()).nodeStructureChanged(root); TreeUtil.traverse(root, new TreeUtil.Traverse() { @Override public boolean accept(Object _node) { final CheckedTreeNode node = (CheckedTreeNode)_node; if (node.isChecked()) { checkboxTree.expandPath(new TreePath(node.getPath()).getParentPath()); } return true; } }); panel.add(ScrollPaneFactory.createScrollPane(checkboxTree)); final Dimension size = checkboxTree.getPreferredSize(); panel.setPreferredSize(new Dimension(size.width + 30, Math.min(size.height + 10, 500))); return panel; } private void addContextNode(MultiMap<TemplateContextType, TemplateContextType> hierarchy, CheckedTreeNode parent, TemplateContextType type) { final Collection<TemplateContextType> children = hierarchy.get(type); final String name = UIUtil.removeMnemonic(type.getPresentableName()); final CheckedTreeNode node = new CheckedTreeNode(Pair.create(children.isEmpty() ? type : null, name)); parent.add(node); if (children.isEmpty()) { node.setChecked(myContext.get(type)); } else { for (TemplateContextType child : children) { addContextNode(hierarchy, node, child); } final CheckedTreeNode other = new CheckedTreeNode(Pair.create(type, "Other")); other.setChecked(myContext.get(type)); node.add(other); } } private boolean isExpandableFromEditor() { boolean hasNonExpandable = false; for (TemplateContextType type : getApplicableContexts()) { if (type.isExpandableFromEditor()) { return true; } hasNonExpandable = true; } return !hasNonExpandable; } private void updateHighlighter() { List<TemplateContextType> applicableContexts = getApplicableContexts(); if (!applicableContexts.isEmpty()) { TemplateContext contextByType = new TemplateContext(); contextByType.setEnabled(applicableContexts.get(0), true); TemplateEditorUtil.setHighlighter(myTemplateEditor, contextByType); return; } ((EditorEx) myTemplateEditor).repaint(0, myTemplateEditor.getDocument().getTextLength()); } private void validateEditVariablesButton() { myEditVariablesButton.setEnabled(!parseVariables(myTemplateEditor.getDocument().getCharsSequence()).isEmpty()); } void resetUi() { myKeyField.setText(myTemplate.getKey()); myDescription.setText(myTemplate.getDescription()); if(myTemplate.getShortcutChar() == TemplateSettings.DEFAULT_CHAR) { myExpandByCombo.setSelectedItem(myDefaultShortcutItem); } else if(myTemplate.getShortcutChar() == TemplateSettings.TAB_CHAR) { myExpandByCombo.setSelectedItem(TAB); } else if(myTemplate.getShortcutChar() == TemplateSettings.ENTER_CHAR) { myExpandByCombo.setSelectedItem(ENTER); } else { myExpandByCombo.setSelectedItem(SPACE); } CommandProcessor.getInstance().executeCommand( null, new Runnable() { @Override public void run() { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { final Document document = myTemplateEditor.getDocument(); document.replaceString(0, document.getTextLength(), myTemplate.getString()); } }); } }, "", null ); myCbReformat.setSelected(myTemplate.isToReformat()); myCbReformat.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { myTemplate.setToReformat(myCbReformat.isSelected()); } }); myExpandByCombo.setEnabled(isExpandableFromEditor()); updateHighlighter(); validateEditVariablesButton(); } private void editVariables() { ArrayList<Variable> newVariables = updateVariablesByTemplateText(); EditVariableDialog editVariableDialog = new EditVariableDialog(myTemplateEditor, myEditVariablesButton, newVariables, getApplicableContexts()); editVariableDialog.show(); if (editVariableDialog.isOK()) { applyVariables(newVariables); } } private ArrayList<Variable> updateVariablesByTemplateText() { List<Variable> oldVariables = getCurrentVariables(); Set<String> oldVariableNames = ContainerUtil.map2Set(oldVariables, new Function<Variable, String>() { @Override public String fun(Variable variable) { return variable.getName(); } }); ArrayList<Variable> parsedVariables = parseVariables(myTemplateEditor.getDocument().getCharsSequence()); Map<String,String> newVariableNames = new HashMap<String, String>(); for (Object parsedVariable : parsedVariables) { Variable newVariable = (Variable)parsedVariable; String name = newVariable.getName(); newVariableNames.put(name, name); } int oldVariableNumber = 0; for(int i = 0; i < parsedVariables.size(); i++){ Variable variable = parsedVariables.get(i); if(oldVariableNames.contains(variable.getName())) { Variable oldVariable = null; for(;oldVariableNumber<oldVariables.size(); oldVariableNumber++) { oldVariable = oldVariables.get(oldVariableNumber); if(newVariableNames.get(oldVariable.getName()) != null) { break; } oldVariable = null; } oldVariableNumber++; if(oldVariable != null) { parsedVariables.set(i, oldVariable); } } } return parsedVariables; } private List<Variable> getCurrentVariables() { List<Variable> myVariables = new ArrayList<Variable>(); for(int i = 0; i < myTemplate.getVariableCount(); i++) { myVariables.add(new Variable(myTemplate.getVariableNameAt(i), myTemplate.getExpressionStringAt(i), myTemplate.getDefaultValueStringAt(i), myTemplate.isAlwaysStopAt(i))); } return myVariables; } public JTextField getKeyField() { return myKeyField; } public void focusKey() { myKeyField.selectAll(); //todo[peter,kirillk] without these invokeLaters this requestFocus conflicts with com.intellij.openapi.ui.impl.DialogWrapperPeerImpl.MyDialog.MyWindowListener.windowOpened() IdeFocusManager.findInstanceByComponent(myKeyField).requestFocus(myKeyField, true); final ModalityState modalityState = ModalityState.stateForComponent(myKeyField); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { IdeFocusManager.findInstanceByComponent(myKeyField).requestFocus(myKeyField, true); } }, modalityState); } }, modalityState); } }, modalityState); } private static ArrayList<Variable> parseVariables(CharSequence text) { ArrayList<Variable> variables = new ArrayList<Variable>(); TemplateImplUtil.parseVariables(text, variables, TemplateImpl.INTERNAL_VARS_SET); return variables; } }