/* * Copyright 2013-2017 consulo.io * * 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 consulo.csharp.ide.refactoring.changeSignature; import java.awt.BorderLayout; import java.awt.Font; import java.awt.GridLayout; import java.awt.Toolkit; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableColumn; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import consulo.csharp.ide.highlight.check.impl.CS1547; import consulo.csharp.ide.refactoring.util.CSharpNameSuggesterUtil; import consulo.csharp.lang.CSharpFileType; import consulo.csharp.lang.psi.CSharpAccessModifier; import consulo.csharp.lang.psi.CSharpMethodDeclaration; import consulo.csharp.lang.psi.CSharpModifier; import consulo.csharp.lang.psi.CSharpTypeRefPresentationUtil; import consulo.csharp.lang.psi.impl.fragment.CSharpFragmentFactory; import consulo.csharp.lang.psi.impl.fragment.CSharpFragmentFileImpl; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorFontType; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiCodeFragment; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.BaseRefactoringProcessor; import com.intellij.refactoring.changeSignature.CallerChooserBase; import com.intellij.refactoring.changeSignature.ChangeSignatureDialogBase; import com.intellij.refactoring.changeSignature.ChangeSignatureProcessorBase; import com.intellij.refactoring.changeSignature.MethodDescriptor; import com.intellij.refactoring.changeSignature.ParameterTableModelItemBase; import com.intellij.refactoring.ui.ComboBoxVisibilityPanel; import com.intellij.refactoring.ui.VisibilityPanelBase; import com.intellij.ui.EditorTextField; import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.TableColumnAnimator; import com.intellij.ui.table.TableView; import com.intellij.ui.treeStructure.Tree; import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewDescriptor; import com.intellij.util.Consumer; import com.intellij.util.PairFunction; import com.intellij.util.ui.table.JBListTable; import com.intellij.util.ui.table.JBTableRow; import com.intellij.util.ui.table.JBTableRowEditor; import consulo.annotations.RequiredDispatchThread; import consulo.annotations.RequiredReadAction; import consulo.dotnet.psi.DotNetExpression; import consulo.dotnet.psi.DotNetLikeMethodDeclaration; import consulo.dotnet.psi.DotNetParameter; import consulo.dotnet.psi.DotNetType; import consulo.dotnet.resolve.DotNetTypeRef; import consulo.internal.dotnet.msil.decompiler.textBuilder.util.StubBlockUtil; /** * @author VISTALL * @since 20.05.14 */ public class CSharpChangeSignatureDialog extends ChangeSignatureDialogBase<CSharpParameterInfo, DotNetLikeMethodDeclaration, CSharpAccessModifier, CSharpMethodDescriptor, CSharpParameterTableModelItem, CSharpParameterTableModel> { public CSharpChangeSignatureDialog(Project project, CSharpMethodDescriptor method, boolean allowDelegation, PsiElement defaultValueContext) { super(project, method, allowDelegation, defaultValueContext); } @Override protected LanguageFileType getFileType() { return CSharpFileType.INSTANCE; } @Override protected CSharpParameterTableModel createParametersInfoModel(CSharpMethodDescriptor method) { return new CSharpParameterTableModel(getProject(), myDefaultValueContext, myDefaultValueContext); } @Override @RequiredDispatchThread protected BaseRefactoringProcessor createRefactoringProcessor() { CSharpChangeInfo changeInfo = generateChangeInfo(); return new ChangeSignatureProcessorBase(getProject(), changeInfo) { @NotNull @Override protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { return new ChangeSignatureViewDescriptor(myMethod.getMethod()); } }; } @Override protected boolean isListTableViewSupported() { return true; } @Nullable @Override protected JBTableRowEditor getTableEditor(final JTable t, final ParameterTableModelItemBase<CSharpParameterInfo> item) { return new JBTableRowEditor() { private EditorTextField myTypeEditor; private EditorTextField myNameEditor; private EditorTextField myDefaultValueEditor; private ComboBox myModifierComboBox; @Override public void prepareEditor(JTable table, int row) { setLayout(new BorderLayout()); JPanel topPanel = new JPanel(new GridLayout(1, 3)); add(topPanel, BorderLayout.NORTH); myModifierComboBox = new ComboBox(); myModifierComboBox.addItem(null); for(CSharpModifier modifier : CSharpParameterInfo.ourParameterModifiers) { myModifierComboBox.addItem(modifier); } myModifierComboBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if(e.getStateChange() == ItemEvent.SELECTED) { item.parameter.setModifier((CSharpModifier) myModifierComboBox.getSelectedItem()); updateSignature(); } } }); myModifierComboBox.setRenderer(new ListCellRendererWrapper<CSharpModifier>() { @Override public void customize(JList list, CSharpModifier value, int index, boolean selected, boolean hasFocus) { setText(value == null ? "" : value.getPresentableText()); } }); myModifierComboBox.setSelectedItem(item.parameter.getModifier()); topPanel.add(createLabeledPanel("Modifier:", myModifierComboBox)); final Document document = PsiDocumentManager.getInstance(getProject()).getDocument(item.typeCodeFragment); myTypeEditor = new EditorTextField(document, getProject(), getFileType()); myTypeEditor.addDocumentListener(getSignatureUpdater()); myTypeEditor.setPreferredWidth(t.getWidth() / 2); myTypeEditor.addDocumentListener(new RowEditorChangeListener(0)); topPanel.add(createLabeledPanel("Type:", myTypeEditor)); myNameEditor = new EditorTextField(item.parameter.getName(), getProject(), getFileType()); myNameEditor.addDocumentListener(getSignatureUpdater()); myNameEditor.addDocumentListener(new RowEditorChangeListener(1)); topPanel.add(createLabeledPanel("Name:", myNameEditor)); if(item.parameter.getOldIndex() == -1) { final JPanel additionalPanel = new JPanel(new BorderLayout()); final Document doc = PsiDocumentManager.getInstance(getProject()).getDocument(item.defaultValueCodeFragment); myDefaultValueEditor = new EditorTextField(doc, getProject(), getFileType()); myDefaultValueEditor.setPreferredWidth(t.getWidth() / 3); myDefaultValueEditor.addDocumentListener(new RowEditorChangeListener(2)); additionalPanel.add(createLabeledPanel("Argument value:", myDefaultValueEditor), BorderLayout.EAST); add(additionalPanel, BorderLayout.SOUTH); } } @Override public JBTableRow getValue() { return new JBTableRow() { @Override public Object getValueAt(int column) { switch(column) { case 0: return item.typeCodeFragment; case 1: return myNameEditor.getText().trim(); case 2: return item.defaultValueCodeFragment; case 3: return myModifierComboBox.getSelectedItem(); } return null; } }; } @Override public JComponent getPreferredFocusedComponent() { final MouseEvent me = getMouseEvent(); if(me == null) { return myTypeEditor.getFocusTarget(); } final double x = me.getPoint().getX(); return x <= getTypesColumnWidth() ? myTypeEditor.getFocusTarget() : myDefaultValueEditor == null || x <= getNamesColumnWidth() ? myNameEditor.getFocusTarget() : myDefaultValueEditor .getFocusTarget(); } @Override public JComponent[] getFocusableComponents() { final List<JComponent> focusable = new ArrayList<JComponent>(); focusable.add(myTypeEditor.getFocusTarget()); focusable.add(myNameEditor.getFocusTarget()); if(myDefaultValueEditor != null) { focusable.add(myDefaultValueEditor.getFocusTarget()); } focusable.add(myModifierComboBox); return focusable.toArray(new JComponent[focusable.size()]); } }; } @Override protected boolean postponeValidation() { return false; } @Override protected boolean mayPropagateParameters() { return false; } @Override protected boolean isEmptyRow(ParameterTableModelItemBase<CSharpParameterInfo> row) { if(!StringUtil.isEmpty(row.parameter.getName())) { return false; } if(!StringUtil.isEmpty(row.parameter.getTypeText())) { return false; } return true; } @Override @RequiredDispatchThread protected JComponent getRowPresentation(ParameterTableModelItemBase<CSharpParameterInfo> item, boolean selected, final boolean focused) { final String typeText = item.typeCodeFragment.getText(); CSharpModifier modifier = item.parameter.getModifier(); String text = ""; if(modifier != null) { text = modifier.getPresentableText() + " "; } final String separator = StringUtil.repeatSymbol(' ', getTypesMaxLength() - typeText.length() + 1); text += typeText + separator + item.parameter.getName(); final String defaultValue = item.defaultValueCodeFragment.getText(); String tail = ""; if(StringUtil.isNotEmpty(defaultValue)) { tail += " argument value = " + defaultValue; } if(!StringUtil.isEmpty(tail)) { text += " //" + tail; } return JBListTable.createEditorTextFieldPresentation(getProject(), getFileType(), " " + text, selected, focused); } @Override protected void customizeParametersTable(TableView<CSharpParameterTableModelItem> table) { final JTable t = table.getComponent(); final TableColumn defaultValue = t.getColumnModel().getColumn(2); final TableColumn varArg = t.getColumnModel().getColumn(3); t.removeColumn(defaultValue); t.removeColumn(varArg); t.getModel().addTableModelListener(new TableModelListener() { @Override public void tableChanged(TableModelEvent e) { if(e.getType() == TableModelEvent.INSERT) { t.getModel().removeTableModelListener(this); final TableColumnAnimator animator = new TableColumnAnimator(t); animator.setStep(48); animator.addColumn(defaultValue, (t.getWidth() - 48) / 3); animator.addColumn(varArg, 48); animator.startAndDoWhenDone(new Runnable() { @Override public void run() { t.editCellAt(t.getRowCount() - 1, 0); } }); animator.start(); } } }); } private int getTypesColumnWidth() { return getColumnWidth(0); } private int getNamesColumnWidth() { return getColumnWidth(1); } private int getTypesMaxLength() { int len = 0; for(ParameterTableModelItemBase<CSharpParameterInfo> item : myParametersTableModel.getItems()) { final String text = item.typeCodeFragment == null ? null : item.typeCodeFragment.getText(); len = Math.max(len, text == null ? 0 : text.length()); } return len; } private int getNamesMaxLength() { int len = 0; for(ParameterTableModelItemBase<CSharpParameterInfo> item : myParametersTableModel.getItems()) { final String text = item.parameter.getName(); len = Math.max(len, text == null ? 0 : text.length()); } return len; } private int getColumnWidth(int index) { int letters = getTypesMaxLength() + (index == 0 ? 1 : getNamesMaxLength() + 2); Font font = EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.PLAIN); font = new Font(font.getFontName(), font.getStyle(), 12); return letters * Toolkit.getDefaultToolkit().getFontMetrics(font).stringWidth("W"); } @NotNull public DotNetLikeMethodDeclaration getMethodDeclaration() { return myMethod.getMethod(); } @RequiredDispatchThread private CSharpChangeInfo generateChangeInfo() { DotNetLikeMethodDeclaration methodDeclaration = getMethodDeclaration(); String newName = null; if(myMethod.canChangeName()) { String methodName = getMethodName(); if(!Comparing.equal(methodName, methodDeclaration.getName())) { newName = methodName; } } String newReturnType = null; if(myMethod.canChangeReturnType() == MethodDescriptor.ReadWriteOption.ReadWrite) { String returnType = myReturnTypeField.getText(); if(!Comparing.equal(typeText(methodDeclaration.getReturnTypeRef()), returnType)) { newReturnType = returnType; } } CSharpAccessModifier newVisibility = null; if(myMethod.canChangeVisibility()) { CSharpAccessModifier visibility = getVisibility(); if(myMethod.getVisibility() != visibility) { newVisibility = visibility; } } boolean parametersChanged = false; List<CSharpParameterInfo> parameters = getParameters(); DotNetParameter[] psiParameters = methodDeclaration.getParameters(); if(parameters.size() != psiParameters.length) { parametersChanged = true; } else { for(int i = 0; i < parameters.size(); i++) { DotNetParameter psiParameter = psiParameters[i]; CSharpParameterInfo newParameter = parameters.get(i); if(!Comparing.equal(newParameter.getName(), psiParameter.getName())) { parametersChanged = true; break; } if(!Comparing.equal(newParameter.getTypeText(), typeText(psiParameter.toTypeRef(false)))) { parametersChanged = true; break; } if(!Comparing.equal(newParameter.getModifier(), CSharpParameterInfo.findModifier(psiParameter))) { parametersChanged = true; break; } } } return new CSharpChangeInfo(methodDeclaration, parameters, parametersChanged, newName, newReturnType, newVisibility); } @RequiredReadAction private String typeText(@NotNull DotNetTypeRef typeRef) { return CSharpTypeRefPresentationUtil.buildShortText(typeRef, myDefaultValueContext); } @Override @NotNull @RequiredDispatchThread public List<CSharpParameterInfo> getParameters() { List<CSharpParameterInfo> result = new ArrayList<CSharpParameterInfo>(myParametersTableModel.getRowCount()); int i = 0; for(ParameterTableModelItemBase<CSharpParameterInfo> item : myParametersTableModel.getItems()) { CSharpParameterInfo e = new CSharpParameterInfo(item.parameter.getName(), item.parameter.getParameter(), i++); DotNetType type = PsiTreeUtil.getChildOfType(item.typeCodeFragment, DotNetType.class); e.setTypeText(type == null ? "" : type.getText()); e.setModifier(item.parameter.getModifier()); e.setTypeRef(type == null ? null : type.toTypeRef()); DotNetExpression expression = PsiTreeUtil.getChildOfType(item.defaultValueCodeFragment, DotNetExpression.class); e.setDefaultValue(expression == null ? "" : expression.getText()); result.add(e); } return result; } @Override @RequiredDispatchThread protected PsiCodeFragment createReturnTypeCodeFragment() { String text = CSharpTypeRefPresentationUtil.buildShortText(myMethod.getMethod().getReturnTypeRef(), myDefaultValueContext); CSharpFragmentFileImpl typeFragment = CSharpFragmentFactory.createTypeFragment(getProject(), text, myDefaultValueContext); typeFragment.putUserData(CS1547.ourReturnTypeFlag, Boolean.TRUE); return typeFragment; } @Nullable @Override protected CallerChooserBase<DotNetLikeMethodDeclaration> createCallerChooser(String title, Tree treeToReuse, Consumer<Set<DotNetLikeMethodDeclaration>> callback) { return null; } @Nullable @Override @RequiredDispatchThread protected String validateAndCommitData() { String methodName = getMethodName(); if(StringUtil.isEmpty(methodName) || CSharpNameSuggesterUtil.isKeyword(methodName)) { return "Bad method name"; } for(CSharpParameterInfo parameterInfo : getParameters()) { String name = parameterInfo.getName(); if(StringUtil.isEmpty(name) || CSharpNameSuggesterUtil.isKeyword(name)) { return "Bad parameter name"; } if(parameterInfo.getTypeRef() == null) { return "Parameter '" + name + "' have bad type"; } } return null; } @Override @RequiredDispatchThread protected String calculateSignature() { DotNetLikeMethodDeclaration methodDeclaration = getMethodDeclaration(); CSharpChangeInfo sharpChangeInfo = generateChangeInfo(); CSharpAccessModifier newVisibility = sharpChangeInfo.getNewVisibility(); StringBuilder builder = new StringBuilder(); if(newVisibility != null) { builder.append(newVisibility.getPresentableText()).append(" "); } else { builder.append(myMethod.getVisibility().getPresentableText()).append(" "); } if(methodDeclaration instanceof CSharpMethodDeclaration) { if(sharpChangeInfo.isReturnTypeChanged()) { builder.append(sharpChangeInfo.getNewReturnType()).append(" "); } else { builder.append(typeText(methodDeclaration.getReturnTypeRef())).append(" "); } } if(sharpChangeInfo.isNameChanged()) { builder.append(sharpChangeInfo.getNewName()); } else { builder.append(methodDeclaration.getName()); } builder.append("("); StubBlockUtil.join(builder, sharpChangeInfo.getNewParameters(), new PairFunction<StringBuilder, CSharpParameterInfo, Void>() { @Nullable @Override public Void fun(StringBuilder b, CSharpParameterInfo parameterInfo) { CSharpModifier modifier = parameterInfo.getModifier(); if(modifier != null) { b.append(modifier.getPresentableText()).append(" "); } b.append(parameterInfo.getTypeText()); b.append(" "); b.append(parameterInfo.getName()); return null; } }, ", "); builder.append(");"); return builder.toString(); } @Override protected VisibilityPanelBase<CSharpAccessModifier> createVisibilityControl() { return new ComboBoxVisibilityPanel<CSharpAccessModifier>(CSharpAccessModifier.VALUES) { @Override protected ListCellRendererWrapper<CSharpAccessModifier> getRenderer() { return new ListCellRendererWrapper<CSharpAccessModifier>() { @Override public void customize(JList list, CSharpAccessModifier value, int index, boolean selected, boolean hasFocus) { setText(value.getPresentableText()); } }; } }; } }