blob: 134143df4782cac8fef8c292f2914a92ab052214 [file] [log] [blame]
/*
* 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.ComboBox;
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 ComboBox(new Object[]{myDefaultShortcutItem, SPACE, TAB, ENTER}, -1);
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() {
myExpandByCombo.setEnabled(isExpandableFromEditor());
updateHighlighter();
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, myContext);
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;
}
static JPanel createPopupContextPanel(final Runnable onChange, final Map<TemplateContextType, Boolean> context) {
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 : context.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) {
context.put(type, node.isChecked());
}
onChange.run();
}
};
for (TemplateContextType type : hierarchy.get(null)) {
addContextNode(hierarchy, root, type, context);
}
((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 static void addContextNode(MultiMap<TemplateContextType, TemplateContextType> hierarchy,
CheckedTreeNode parent,
TemplateContextType type, Map<TemplateContextType, Boolean> context) {
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(context.get(type));
}
else {
for (TemplateContextType child : children) {
addContextNode(hierarchy, node, child, context);
}
final CheckedTreeNode other = new CheckedTreeNode(Pair.create(type, "Other"));
other.setChecked(context.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;
}
}