Move: Implement member selection UI

This commit is contained in:
Alexey Sedunov
2015-04-27 18:04:36 +03:00
parent d096e74c10
commit ee83435830
6 changed files with 214 additions and 37 deletions
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.idea.refactoring
import com.intellij.refactoring.classMembers.MemberInfoBase
import com.intellij.refactoring.classMembers.MemberInfoModel
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetNamedDeclaration
public class KotlinMemberInfo(member: JetNamedDeclaration) : MemberInfoBase<JetNamedDeclaration>(member) {
init {
isStatic = member.getParent() is JetFile
displayName = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.render(member.resolveToDescriptor())
}
}
@@ -31,7 +31,6 @@ import com.intellij.psi.PsiPackage
import com.intellij.psi.PsiDirectory
import org.jetbrains.kotlin.psi.psiUtil.getPackage
import org.jetbrains.kotlin.idea.refactoring.move.getFileNameAfterMove
import java.util.HashSet
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.util.CommonRefactoringUtil
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesImpl
@@ -41,6 +40,7 @@ import org.jetbrains.kotlin.psi.JetNamedFunction
import org.jetbrains.kotlin.psi.JetProperty
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog
import java.util.*
public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() {
private fun doMoveWithCheck(
@@ -68,7 +68,14 @@ public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() {
if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, elements.toList(), true)) return false
[suppress("UNCHECKED_CAST")]
val elementsToSearch = elements.toList() as List<JetNamedDeclaration>
val elementsToSearch = elements.toSet() as Set<JetNamedDeclaration>
val sourceFile = elementsToSearch.mapTo(LinkedHashSet<JetFile>()) { it.getContainingJetFile() }.singleOrNull()
if (sourceFile == null) {
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("move.title"), "All declarations must belong to the same file", null, project)
return false
}
val targetPackageName = MoveClassesOrPackagesImpl.getInitialTargetPackageName(targetContainer, elements)
val targetDirectory = MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements)
val searchInComments = JavaRefactoringSettings.getInstance()!!.MOVE_SEARCH_IN_COMMENTS
@@ -83,7 +90,7 @@ public class MoveKotlinTopLevelDeclarationsHandler : MoveHandlerDelegate() {
val moveToPackage = targetContainer !is JetFile
MoveKotlinTopLevelDeclarationsDialog(
project, elementsToSearch, targetPackageName, targetDirectory, targetFile, moveToPackage, searchInComments, searchInText, callback
project, sourceFile, elementsToSearch, targetPackageName, targetDirectory, targetFile, moveToPackage, searchInComments, searchInText, callback
).show()
return true
@@ -10,16 +10,6 @@
<color color="-8355712"/>
</border>
<children>
<component id="ec4f" class="javax.swing.JLabel" binding="elementDescription">
<constraints>
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<enabled value="true"/>
<font style="1"/>
<text value="Move"/>
</properties>
</component>
<component id="74471" class="com.intellij.ui.NonFocusableCheckBox" binding="cbSearchInComments">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="3" indent="0" use-parent-layout="false"/>
@@ -106,6 +96,14 @@
</component>
</children>
</grid>
<grid id="c5589" binding="memberInfoPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children/>
</grid>
</children>
</grid>
<buttonGroups>
@@ -34,6 +34,9 @@ import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.MoveDestination;
import com.intellij.refactoring.PackageWrapper;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.DependencyMemberInfoModel;
import com.intellij.refactoring.classMembers.MemberInfoChange;
import com.intellij.refactoring.classMembers.UsesMemberDependencyGraph;
import com.intellij.refactoring.move.MoveCallback;
import com.intellij.refactoring.move.MoveHandler;
import com.intellij.refactoring.move.moveClassesOrPackages.DestinationFolderComboBox;
@@ -43,29 +46,51 @@ import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.ui.ComboboxWithBrowseButton;
import com.intellij.ui.RecentsManager;
import com.intellij.ui.ReferenceEditorComboWithBrowseButton;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ui.UIUtil;
import kotlin.Function0;
import kotlin.Function1;
import kotlin.KotlinPackage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.core.refactoring.RefactoringPackage;
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle;
import org.jetbrains.kotlin.idea.refactoring.KotlinMemberInfo;
import org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.*;
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinMemberSelectionPanel;
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinMemberSelectionTable;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.JetFile;
import org.jetbrains.kotlin.psi.JetNamedDeclaration;
import org.jetbrains.kotlin.idea.core.refactoring.RefactoringPackage;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.List;
import java.util.Set;
public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
private static final String RECENTS_KEY = "MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY";
private JLabel elementDescription;
private class MemberInfoModelImpl extends DependencyMemberInfoModel<JetNamedDeclaration, KotlinMemberInfo> {
public MemberInfoModelImpl() {
super(new UsesMemberDependencyGraph<JetNamedDeclaration, JetFile, KotlinMemberInfo>(sourceFile, null, false), WARNING);
}
@Override
@Nullable
public Boolean isFixedAbstract(KotlinMemberInfo member) {
return null;
}
@Override
public boolean isCheckedWhenDisabled(KotlinMemberInfo member) {
return false;
}
}
private JCheckBox cbSearchInComments;
private JCheckBox cbSearchTextOccurences;
private JPanel mainPanel;
@@ -76,13 +101,16 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
private JRadioButton rbMoveToPackage;
private JRadioButton rbMoveToFile;
private TextFieldWithBrowseButton fileChooser;
private JPanel memberInfoPanel;
private KotlinMemberSelectionTable memberTable;
private final List<JetNamedDeclaration> elementsToMove;
private final JetFile sourceFile;
private final MoveCallback moveCallback;
public MoveKotlinTopLevelDeclarationsDialog(
@NotNull Project project,
@NotNull List<JetNamedDeclaration> elementsToMove,
@NotNull JetFile sourceFile,
@NotNull Set<JetNamedDeclaration> elementsToMove,
@Nullable String targetPackageName,
@Nullable PsiDirectory targetDirectory,
@Nullable JetFile targetFile,
@@ -93,15 +121,13 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
) {
super(project, true);
this.elementsToMove = elementsToMove;
this.sourceFile = sourceFile;
this.moveCallback = moveCallback;
init();
setTitle(MoveHandler.REFACTORING_NAME);
initElementDescription(elementsToMove);
initSearchOptions(searchInComments, searchForTextOccurences);
initPackageChooser(targetPackageName, targetDirectory);
@@ -110,7 +136,32 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
initMoveToButtons(moveToPackage);
initMemberInfo(elementsToMove);
updateControls();
pack();
}
private void initMemberInfo(@NotNull final Set<JetNamedDeclaration> elementsToMove) {
List<KotlinMemberInfo> memberInfos = KotlinPackage.map(
KotlinPackage.filterIsInstance(sourceFile.getDeclarations(), JetNamedDeclaration.class),
new Function1<JetNamedDeclaration, KotlinMemberInfo>() {
@Override
public KotlinMemberInfo invoke(JetNamedDeclaration declaration) {
KotlinMemberInfo memberInfo = new KotlinMemberInfo(declaration);
memberInfo.setChecked(elementsToMove.contains(declaration));
return memberInfo;
}
}
);
KotlinMemberSelectionPanel selectionPanel = new KotlinMemberSelectionPanel(getTitle(), memberInfos, null);
memberTable = selectionPanel.getTable();
MemberInfoModelImpl memberInfoModel = new MemberInfoModelImpl();
memberInfoModel.memberInfoChanged(new MemberInfoChange<JetNamedDeclaration, KotlinMemberInfo>(memberInfos));
selectionPanel.getTable().setMemberInfoModel(memberInfoModel);
selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel);
memberInfoPanel.add(selectionPanel, BorderLayout.CENTER);
}
private void initPackageChooser(String targetPackageName, PsiDirectory targetDirectory) {
@@ -177,22 +228,6 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
}
}
private void initElementDescription(List<JetNamedDeclaration> elementsToMove) {
if (elementsToMove.size() == 1) {
PsiElement element = elementsToMove.get(0);
elementDescription.setText(
JetRefactoringBundle.message(
"refactoring.move.specifc.element",
UsageViewUtil.getType(element),
UsageViewUtil.getLongName(element)
)
);
}
else if (elementsToMove.size() > 1) {
elementDescription.setText(JetRefactoringBundle.message("refactoring.move.selected.elements"));
}
}
private void createUIComponents() {
classPackageChooser = createPackageChooser();
@@ -298,6 +333,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
@Nullable
private String verifyBeforeRun() {
if (memberTable.getSelectedMemberInfos().isEmpty()) return "At least one member must be selected";
if (isMoveToPackage()) {
String name = getTargetPackage().trim();
if (name.length() != 0 && !PsiNameHelper.getInstance(myProject).isQualifiedName(name)) {
@@ -314,6 +351,18 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
return null;
}
private List<JetNamedDeclaration> getSelectedElementsToMove() {
return KotlinPackage.map(
memberTable.getSelectedMemberInfos(),
new Function1<KotlinMemberInfo, JetNamedDeclaration>() {
@Override
public JetNamedDeclaration invoke(KotlinMemberInfo info) {
return info.getMember();
}
}
);
}
@Override
protected JComponent createCenterPanel() {
boolean isDestinationVisible = hasAnySourceRoots();
@@ -354,6 +403,9 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
if (target == null) return;
saveRefactoringSettings();
List<JetNamedDeclaration> elementsToMove = getSelectedElementsToMove();
for (PsiElement element : elementsToMove) {
String message = target.verify(element.getContainingFile());
if (message != null) {
@@ -0,0 +1,48 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.idea.refactoring.ui
import com.intellij.refactoring.ui.AbstractMemberSelectionPanel
import com.intellij.ui.ScrollPaneFactory
import com.intellij.ui.SeparatorFactory
import org.jetbrains.kotlin.idea.refactoring.KotlinMemberInfo
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import java.awt.BorderLayout
public class KotlinMemberSelectionPanel(title: String,
memberInfo: List<KotlinMemberInfo>,
abstractColumnHeader: String?
) : AbstractMemberSelectionPanel<JetNamedDeclaration, KotlinMemberInfo>() {
private val table = createMemberSelectionTable(memberInfo, abstractColumnHeader)
init {
setLayout(BorderLayout())
val scrollPane = ScrollPaneFactory.createScrollPane(table)
add(SeparatorFactory.createSeparator(title, table), BorderLayout.NORTH)
add(scrollPane, BorderLayout.CENTER)
}
protected fun createMemberSelectionTable(
memberInfo: List<KotlinMemberInfo>,
abstractColumnHeader: String?
): KotlinMemberSelectionTable {
return KotlinMemberSelectionTable(memberInfo, null, abstractColumnHeader)
}
override fun getTable() = table
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.idea.refactoring.ui
import com.intellij.refactoring.classMembers.MemberInfoModel
import com.intellij.refactoring.ui.AbstractMemberSelectionTable
import com.intellij.ui.RowIcon
import com.intellij.util.IconUtil
import org.jetbrains.kotlin.idea.JetIconProvider
import org.jetbrains.kotlin.idea.refactoring.KotlinMemberInfo
import org.jetbrains.kotlin.psi.JetNamedDeclaration
public class KotlinMemberSelectionTable(
memberInfos: List<KotlinMemberInfo>,
memberInfoModel: MemberInfoModel<JetNamedDeclaration, KotlinMemberInfo>?,
abstractColumnHeader: String?
) : AbstractMemberSelectionTable<JetNamedDeclaration, KotlinMemberInfo>(memberInfos, memberInfoModel, abstractColumnHeader) {
override fun getAbstractColumnValue(memberInfo: KotlinMemberInfo) = null
override fun isAbstractColumnEditable(rowIndex: Int) = false
override fun setVisibilityIcon(memberInfo: KotlinMemberInfo, icon: RowIcon) {
icon.setIcon(JetIconProvider.getVisibilityIcon(memberInfo.getMember().getModifierList()), 1);
}
override fun getOverrideIcon(memberInfo: KotlinMemberInfo) = AbstractMemberSelectionTable.EMPTY_OVERRIDE_ICON
}