Move: Implement "Move nested class to another class" UI
#KT-9027 Fixed
This commit is contained in:
+31
-10
@@ -32,8 +32,10 @@ import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackages
|
|||||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||||
import org.jetbrains.kotlin.idea.core.getPackage
|
import org.jetbrains.kotlin.idea.core.getPackage
|
||||||
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
||||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinNestedClassesToUpperLevelDialog
|
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.KotlinSelectNestedClassRefactoringDialog
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinNestedClassesDialog
|
||||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog
|
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||||
@@ -96,13 +98,23 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is KtClassOrObject -> {
|
is KtClassOrObject -> {
|
||||||
if (elementsToSearch.size > 1) return false
|
if (elementsToSearch.size > 1) {
|
||||||
|
// todo: allow moving multiple classes to upper level
|
||||||
val outerClass = container.parent as KtClassOrObject
|
if (targetContainer !is KtClassOrObject) {
|
||||||
val newContainer = targetContainer
|
val message = RefactoringBundle.getCannotRefactorMessage("Moving multiple nested classes to top-level is not supported")
|
||||||
?: outerClass.containingClassOrObject
|
CommonRefactoringUtil.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||||
?: outerClass.containingFile.let { it.containingDirectory ?: it }
|
return true
|
||||||
MoveKotlinNestedClassesToUpperLevelDialog(project, elementsToSearch.first() as KtClassOrObject, newContainer).show()
|
}
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
MoveKotlinNestedClassesDialog(project,
|
||||||
|
elementsToSearch.filterIsInstance<KtClassOrObject>(),
|
||||||
|
container,
|
||||||
|
targetContainer,
|
||||||
|
callback).show()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
KotlinSelectNestedClassRefactoringDialog.chooseNestedClassRefactoring(elementsToSearch.first() as KtClassOrObject,
|
||||||
|
targetContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw AssertionError("Unexpected container: ${container.getElementTextWithContext()}")
|
else -> throw AssertionError("Unexpected container: ${container.getElementTextWithContext()}")
|
||||||
@@ -113,7 +125,11 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
|||||||
|
|
||||||
private fun canMove(elements: Array<out PsiElement>, targetContainer: PsiElement?, editorMode: Boolean): Boolean {
|
private fun canMove(elements: Array<out PsiElement>, targetContainer: PsiElement?, editorMode: Boolean): Boolean {
|
||||||
if (!super.canMove(elements, targetContainer)) return false
|
if (!super.canMove(elements, targetContainer)) return false
|
||||||
if (getUniqueContainer(elements) == null) return false
|
val container = getUniqueContainer(elements) ?: return false
|
||||||
|
|
||||||
|
if (container is KtClassOrObject && targetContainer != null && targetContainer !is KtClassOrObject && elements.size > 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return elements.all { e ->
|
return elements.all { e ->
|
||||||
if (e is KtClass || (e is KtObjectDeclaration && !e.isObjectLiteral()) || e is KtNamedFunction || e is KtProperty) {
|
if (e is KtClass || (e is KtObjectDeclaration && !e.isObjectLiteral()) || e is KtNamedFunction || e is KtProperty) {
|
||||||
@@ -128,7 +144,12 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun isValidTarget(psiElement: PsiElement?, sources: Array<out PsiElement>): Boolean {
|
override fun isValidTarget(psiElement: PsiElement?, sources: Array<out PsiElement>): Boolean {
|
||||||
return psiElement is PsiPackage || (psiElement is PsiDirectory && psiElement.getPackage() != null) || psiElement is KtFile
|
return psiElement is PsiPackage
|
||||||
|
|| (psiElement is PsiDirectory && psiElement.getPackage() != null)
|
||||||
|
|| psiElement is KtFile
|
||||||
|
|| (psiElement is KtClassOrObject
|
||||||
|
&& !(psiElement.hasModifier(KtTokens.ANNOTATION_KEYWORD))
|
||||||
|
&& !sources.any { it.parent is KtFile })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doMove(project: Project, elements: Array<out PsiElement>, targetContainer: PsiElement?, callback: MoveCallback?) {
|
override fun doMove(project: Project, elements: Array<out PsiElement>, targetContainer: PsiElement?, callback: MoveCallback?) {
|
||||||
|
|||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 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.move.moveDeclarations.ui
|
||||||
|
|
||||||
|
import com.intellij.BundleBase
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.ui.DialogWrapper
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.refactoring.RefactoringBundle
|
||||||
|
import com.intellij.refactoring.util.RadioUpDownListener
|
||||||
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import javax.swing.*
|
||||||
|
|
||||||
|
internal class KotlinSelectNestedClassRefactoringDialog private constructor (
|
||||||
|
private val project: Project,
|
||||||
|
private val nestedClass: KtClassOrObject,
|
||||||
|
private val targetContainer: PsiElement?
|
||||||
|
) : DialogWrapper(project, true) {
|
||||||
|
private val moveToUpperLevelButton = JRadioButton()
|
||||||
|
private val moveMembersButton = JRadioButton()
|
||||||
|
|
||||||
|
init {
|
||||||
|
title = RefactoringBundle.message("select.refactoring.title")
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createNorthPanel() = JLabel(RefactoringBundle.message("what.would.you.like.to.do"))
|
||||||
|
|
||||||
|
override fun getPreferredFocusedComponent() = moveToUpperLevelButton
|
||||||
|
|
||||||
|
override fun getDimensionServiceKey(): String {
|
||||||
|
return "#org.jetbrains.kotlin.idea.refactoring.move.moveTopLevelDeclarations.ui.KotlinSelectInnerOrMembersRefactoringDialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createCenterPanel(): JComponent? {
|
||||||
|
moveToUpperLevelButton.text = BundleBase.replaceMnemonicAmpersand("Move &nested class ${nestedClass.name} to upper level")
|
||||||
|
moveToUpperLevelButton.isSelected = true
|
||||||
|
|
||||||
|
moveMembersButton.text = BundleBase.replaceMnemonicAmpersand("&Move nested class ${nestedClass.name} to another class")
|
||||||
|
|
||||||
|
ButtonGroup().apply {
|
||||||
|
add(moveToUpperLevelButton)
|
||||||
|
add(moveMembersButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
RadioUpDownListener(moveToUpperLevelButton, moveMembersButton)
|
||||||
|
|
||||||
|
return JPanel(BorderLayout()).apply {
|
||||||
|
val box = Box.createVerticalBox().apply {
|
||||||
|
add(Box.createVerticalStrut(5))
|
||||||
|
add(moveToUpperLevelButton)
|
||||||
|
add(moveMembersButton)
|
||||||
|
}
|
||||||
|
add(box, BorderLayout.CENTER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNextDialog(): DialogWrapper? {
|
||||||
|
return when {
|
||||||
|
moveToUpperLevelButton.isSelected -> MoveKotlinNestedClassesToUpperLevelDialog(nestedClass, targetContainer)
|
||||||
|
moveMembersButton.isSelected -> MoveKotlinNestedClassesDialog(nestedClass, targetContainer)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun MoveKotlinNestedClassesToUpperLevelDialog(
|
||||||
|
nestedClass: KtClassOrObject,
|
||||||
|
targetContainer: PsiElement?
|
||||||
|
): MoveKotlinNestedClassesToUpperLevelDialog {
|
||||||
|
val outerClass = nestedClass.containingClassOrObject!!
|
||||||
|
val newTarget = targetContainer
|
||||||
|
?: outerClass.containingClassOrObject
|
||||||
|
?: outerClass.containingFile.let { it.containingDirectory ?: it }
|
||||||
|
return MoveKotlinNestedClassesToUpperLevelDialog(nestedClass.project, nestedClass, newTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MoveKotlinNestedClassesDialog(
|
||||||
|
nestedClass: KtClassOrObject,
|
||||||
|
targetContainer: PsiElement?
|
||||||
|
): MoveKotlinNestedClassesDialog {
|
||||||
|
return MoveKotlinNestedClassesDialog(nestedClass.project,
|
||||||
|
listOf(nestedClass),
|
||||||
|
nestedClass.containingClassOrObject!!,
|
||||||
|
targetContainer as? KtClassOrObject ?: nestedClass.containingClassOrObject!!,
|
||||||
|
null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun chooseNestedClassRefactoring(nestedClass: KtClassOrObject, targetContainer: PsiElement?) {
|
||||||
|
val project = nestedClass.project
|
||||||
|
val dialog = when {
|
||||||
|
targetContainer != null && targetContainer !is KtClassOrObject ||
|
||||||
|
nestedClass is KtClass && nestedClass.isInner() -> {
|
||||||
|
MoveKotlinNestedClassesToUpperLevelDialog(nestedClass, targetContainer)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val selectionDialog = KotlinSelectNestedClassRefactoringDialog(project, nestedClass, targetContainer)
|
||||||
|
selectionDialog.show()
|
||||||
|
if (selectionDialog.exitCode != OK_EXIT_CODE) return
|
||||||
|
selectionDialog.getNextDialog() ?: return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinNestedClassesDialog">
|
||||||
|
<grid id="1a398" binding="mainPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||||
|
<margin top="0" left="0" bottom="0" right="0"/>
|
||||||
|
<constraints>
|
||||||
|
<xy x="10" y="10" width="472" height="414"/>
|
||||||
|
</constraints>
|
||||||
|
<properties/>
|
||||||
|
<border type="none">
|
||||||
|
<color color="-8355712"/>
|
||||||
|
</border>
|
||||||
|
<children>
|
||||||
|
<grid id="64650" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||||
|
<margin top="5" left="5" bottom="5" right="5"/>
|
||||||
|
<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>
|
||||||
|
<component id="c6d13" class="javax.swing.JLabel">
|
||||||
|
<constraints>
|
||||||
|
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||||
|
</constraints>
|
||||||
|
<properties>
|
||||||
|
<labelFor value="50ecd"/>
|
||||||
|
<text value="Move members from:"/>
|
||||||
|
</properties>
|
||||||
|
</component>
|
||||||
|
<component id="50ecd" class="javax.swing.JTextField" binding="originalClassField">
|
||||||
|
<constraints>
|
||||||
|
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||||
|
<preferred-size width="150" height="-1"/>
|
||||||
|
</grid>
|
||||||
|
</constraints>
|
||||||
|
<properties>
|
||||||
|
<editable value="false"/>
|
||||||
|
</properties>
|
||||||
|
</component>
|
||||||
|
</children>
|
||||||
|
</grid>
|
||||||
|
<vspacer id="e0fb3">
|
||||||
|
<constraints>
|
||||||
|
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||||
|
</constraints>
|
||||||
|
</vspacer>
|
||||||
|
<grid id="e4ac9" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||||
|
<margin top="0" left="0" bottom="0" right="0"/>
|
||||||
|
<constraints>
|
||||||
|
<grid row="1" 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>
|
||||||
|
<component id="393db" class="javax.swing.JLabel">
|
||||||
|
<constraints>
|
||||||
|
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||||
|
</constraints>
|
||||||
|
<properties>
|
||||||
|
<text value="To (fully qualified name):"/>
|
||||||
|
</properties>
|
||||||
|
</component>
|
||||||
|
<grid id="1f1bc" binding="targetClassChooserPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||||
|
<constraints>
|
||||||
|
<grid row="1" column="0" row-span="1" col-span="1" 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>
|
||||||
|
<grid id="77b3c" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||||
|
<margin top="0" left="0" bottom="0" right="0"/>
|
||||||
|
<constraints>
|
||||||
|
<grid row="3" 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>
|
||||||
|
<component id="fd527" class="javax.swing.JCheckBox" binding="openInEditorCheckBox">
|
||||||
|
<constraints>
|
||||||
|
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||||
|
</constraints>
|
||||||
|
<properties>
|
||||||
|
<selected value="true"/>
|
||||||
|
<text value="Open moved members in editor"/>
|
||||||
|
</properties>
|
||||||
|
</component>
|
||||||
|
<hspacer id="cdd1d">
|
||||||
|
<constraints>
|
||||||
|
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||||
|
</constraints>
|
||||||
|
</hspacer>
|
||||||
|
</children>
|
||||||
|
</grid>
|
||||||
|
<grid id="a6a2e" binding="membersInfoPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||||
|
<constraints>
|
||||||
|
<grid row="2" column="0" row-span="1" col-span="1" 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>
|
||||||
|
<group name="buttonGroup1">
|
||||||
|
<member id="765d3"/>
|
||||||
|
<member id="58e61"/>
|
||||||
|
</group>
|
||||||
|
</buttonGroups>
|
||||||
|
</form>
|
||||||
+256
@@ -0,0 +1,256 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 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.move.moveDeclarations.ui;
|
||||||
|
|
||||||
|
import com.intellij.ide.util.ClassFilter;
|
||||||
|
import com.intellij.ide.util.TreeClassChooser;
|
||||||
|
import com.intellij.ide.util.TreeJavaClassChooserDialog;
|
||||||
|
import com.intellij.openapi.editor.event.DocumentAdapter;
|
||||||
|
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||||
|
import com.intellij.openapi.options.ConfigurationException;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.psi.JavaCodeFragment;
|
||||||
|
import com.intellij.psi.JavaPsiFacade;
|
||||||
|
import com.intellij.psi.PsiClass;
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope;
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
|
import com.intellij.refactoring.RefactoringBundle;
|
||||||
|
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel;
|
||||||
|
import com.intellij.refactoring.classMembers.MemberInfoChange;
|
||||||
|
import com.intellij.refactoring.move.MoveCallback;
|
||||||
|
import com.intellij.refactoring.move.MoveHandler;
|
||||||
|
import com.intellij.refactoring.ui.RefactoringDialog;
|
||||||
|
import com.intellij.ui.ReferenceEditorComboWithBrowseButton;
|
||||||
|
import kotlin.collections.CollectionsKt;
|
||||||
|
import kotlin.jvm.functions.Function1;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.kotlin.asJava.KtLightClassForExplicitDeclaration;
|
||||||
|
import org.jetbrains.kotlin.asJava.LightClassUtil;
|
||||||
|
import org.jetbrains.kotlin.asJava.LightClassUtilsKt;
|
||||||
|
import org.jetbrains.kotlin.idea.projectView.KtClassOrObjectTreeNode;
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo;
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel;
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable;
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*;
|
||||||
|
import org.jetbrains.kotlin.psi.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MoveKotlinNestedClassesDialog extends RefactoringDialog {
|
||||||
|
private static final String RECENTS_KEY = MoveKotlinNestedClassesDialog.class.getName() + ".RECENTS_KEY";
|
||||||
|
|
||||||
|
private static class MemberInfoModelImpl extends AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel mainPanel;
|
||||||
|
private JTextField originalClassField;
|
||||||
|
private JPanel membersInfoPanel;
|
||||||
|
private ReferenceEditorComboWithBrowseButton targetClassChooser;
|
||||||
|
private JCheckBox openInEditorCheckBox;
|
||||||
|
private JPanel targetClassChooserPanel;
|
||||||
|
private KotlinMemberSelectionTable memberTable;
|
||||||
|
|
||||||
|
private final KtClassOrObject originalClass;
|
||||||
|
private KtClassOrObject targetClass;
|
||||||
|
private final MoveCallback moveCallback;
|
||||||
|
|
||||||
|
public MoveKotlinNestedClassesDialog(
|
||||||
|
@NotNull Project project,
|
||||||
|
@NotNull List<KtClassOrObject> elementsToMove,
|
||||||
|
@NotNull KtClassOrObject originalClass,
|
||||||
|
@NotNull KtClassOrObject targetClass,
|
||||||
|
@Nullable MoveCallback moveCallback
|
||||||
|
) {
|
||||||
|
super(project, true);
|
||||||
|
|
||||||
|
this.originalClass = originalClass;
|
||||||
|
this.targetClass = targetClass;
|
||||||
|
this.moveCallback = moveCallback;
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
setTitle(MoveHandler.REFACTORING_NAME);
|
||||||
|
|
||||||
|
initClassChooser(targetClass);
|
||||||
|
|
||||||
|
initMemberInfo(elementsToMove);
|
||||||
|
|
||||||
|
validateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initClassChooser(@NotNull KtClassOrObject initialTargetClass) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
originalClassField.setText(originalClass.getFqName().asString());
|
||||||
|
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
targetClassChooser = new ReferenceEditorComboWithBrowseButton(
|
||||||
|
new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
TreeClassChooser chooser = new TreeJavaClassChooserDialog(
|
||||||
|
RefactoringBundle.message("choose.destination.class"),
|
||||||
|
myProject,
|
||||||
|
GlobalSearchScope.projectScope(myProject),
|
||||||
|
new ClassFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean isAccepted(PsiClass aClass) {
|
||||||
|
if (!(aClass instanceof KtLightClassForExplicitDeclaration)) return false;
|
||||||
|
KtClassOrObject classOrObject = ((KtLightClassForExplicitDeclaration) aClass).getOrigin();
|
||||||
|
|
||||||
|
if (classOrObject.isTopLevel()) return true;
|
||||||
|
|
||||||
|
if (classOrObject instanceof KtObjectDeclaration) {
|
||||||
|
return !((KtObjectDeclaration) classOrObject).isObjectLiteral();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (classOrObject instanceof KtClass) {
|
||||||
|
return !((KtClass) classOrObject).isInner();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
) {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
protected PsiClass getSelectedFromTreeUserObject(DefaultMutableTreeNode node) {
|
||||||
|
PsiClass psiClass = super.getSelectedFromTreeUserObject(node);
|
||||||
|
if (psiClass != null) return psiClass;
|
||||||
|
|
||||||
|
Object userObject = node.getUserObject();
|
||||||
|
if (!(userObject instanceof KtClassOrObjectTreeNode)) return null;
|
||||||
|
return LightClassUtilsKt.toLightClass(((KtClassOrObjectTreeNode) userObject).getValue());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
chooser.selectDirectory((targetClass != null ? targetClass : originalClass).getContainingFile().getContainingDirectory());
|
||||||
|
chooser.showDialog();
|
||||||
|
|
||||||
|
PsiClass aClass = chooser.getSelected();
|
||||||
|
if (aClass instanceof KtLightClassForExplicitDeclaration) {
|
||||||
|
targetClass = ((KtLightClassForExplicitDeclaration) aClass).getOrigin();
|
||||||
|
targetClassChooser.setText(aClass.getQualifiedName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initialTargetClass.getFqName().asString(),
|
||||||
|
myProject,
|
||||||
|
true,
|
||||||
|
JavaCodeFragment.VisibilityChecker.PROJECT_SCOPE_VISIBLE,
|
||||||
|
RECENTS_KEY);
|
||||||
|
targetClassChooser.getChildComponent().getDocument().addDocumentListener(
|
||||||
|
new DocumentAdapter() {
|
||||||
|
@Override
|
||||||
|
public void documentChanged(DocumentEvent e) {
|
||||||
|
PsiClass aClass = JavaPsiFacade
|
||||||
|
.getInstance(myProject)
|
||||||
|
.findClass(targetClassChooser.getText(), GlobalSearchScope.projectScope(myProject));
|
||||||
|
targetClass = aClass instanceof KtLightClassForExplicitDeclaration
|
||||||
|
? ((KtLightClassForExplicitDeclaration) aClass).getOrigin()
|
||||||
|
: null;
|
||||||
|
validateButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
targetClassChooserPanel.add(targetClassChooser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initMemberInfo(@NotNull final List<KtClassOrObject> elementsToMove) {
|
||||||
|
List<KotlinMemberInfo> memberInfos = CollectionsKt.mapNotNull(
|
||||||
|
originalClass.getDeclarations(),
|
||||||
|
new Function1<KtDeclaration, KotlinMemberInfo>() {
|
||||||
|
@Override
|
||||||
|
public KotlinMemberInfo invoke(KtDeclaration declaration) {
|
||||||
|
if (!(declaration instanceof KtClassOrObject)) return null;
|
||||||
|
|
||||||
|
KotlinMemberInfo memberInfo = new KotlinMemberInfo((KtClassOrObject) declaration, false);
|
||||||
|
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<KtNamedDeclaration, KotlinMemberInfo>(memberInfos));
|
||||||
|
selectionPanel.getTable().setMemberInfoModel(memberInfoModel);
|
||||||
|
selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel);
|
||||||
|
membersInfoPanel.add(selectionPanel, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<KtClassOrObject> getSelectedElementsToMove() {
|
||||||
|
return CollectionsKt.map(
|
||||||
|
memberTable.getSelectedMemberInfos(),
|
||||||
|
new Function1<KotlinMemberInfo, KtClassOrObject>() {
|
||||||
|
@Override
|
||||||
|
public KtClassOrObject invoke(KotlinMemberInfo info) {
|
||||||
|
return (KtClassOrObject) info.getMember();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JComponent createCenterPanel() {
|
||||||
|
return mainPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDimensionServiceKey() {
|
||||||
|
return "#" + getClass().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void canRun() throws ConfigurationException {
|
||||||
|
if (targetClass == null) throw new ConfigurationException("No destination class specified");
|
||||||
|
|
||||||
|
if (originalClass == targetClass) {
|
||||||
|
throw new ConfigurationException(RefactoringBundle.message("source.and.destination.classes.should.be.different"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (KtClassOrObject classOrObject : getSelectedElementsToMove()) {
|
||||||
|
if (PsiTreeUtil.isAncestor(classOrObject, targetClass, false)) {
|
||||||
|
throw new ConfigurationException("Cannot move nested class " + classOrObject.getName() + " to itself");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doAction() {
|
||||||
|
List<KtClassOrObject> elementsToMove = getSelectedElementsToMove();
|
||||||
|
KotlinMoveTarget target = new KotlinMoveTargetForExistingElement(targetClass);
|
||||||
|
MoveDeclarationsDelegate.NestedClass delegate = new MoveDeclarationsDelegate.NestedClass();
|
||||||
|
MoveDeclarationsDescriptor descriptor = new MoveDeclarationsDescriptor(
|
||||||
|
elementsToMove, target, delegate, false, false, true, false, moveCallback, openInEditorCheckBox.isSelected()
|
||||||
|
);
|
||||||
|
invokeRefactoring(new MoveKotlinDeclarationsProcessor(myProject, descriptor, Mover.Default.INSTANCE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JComponent getPreferredFocusedComponent() {
|
||||||
|
return targetClassChooser.getChildComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user