2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.jetbrains.python.refactoring.move;
18 import com.intellij.ide.util.PropertiesComponent;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.ui.DialogWrapperPeer;
21 import com.intellij.psi.PsiNamedElement;
22 import com.intellij.refactoring.classMembers.MemberInfoChange;
23 import com.intellij.refactoring.classMembers.MemberInfoModel;
24 import com.intellij.refactoring.ui.AbstractMemberSelectionTable;
25 import com.intellij.ui.HideableDecorator;
26 import com.intellij.ui.RowIcon;
27 import com.intellij.ui.components.JBScrollPane;
28 import com.intellij.util.Function;
29 import com.intellij.util.containers.ContainerUtil;
30 import com.jetbrains.python.PyBundle;
31 import com.jetbrains.python.psi.PyClass;
32 import com.jetbrains.python.psi.PyElement;
33 import com.jetbrains.python.psi.PyFile;
34 import com.jetbrains.python.psi.PyFunction;
35 import com.jetbrains.python.psi.impl.PyPsiUtils;
36 import org.jetbrains.annotations.NonNls;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39 import org.jetbrains.annotations.TestOnly;
42 import javax.swing.event.TableModelEvent;
43 import javax.swing.event.TableModelListener;
45 import java.util.Collection;
46 import java.util.Comparator;
47 import java.util.List;
50 * @author Mikhail Golubev
52 public class PyMoveModuleMembersDialog extends PyBaseMoveDialog {
53 @NonNls private final static String BULK_MOVE_TABLE_VISIBLE = "python.move.module.members.dialog.show.table";
56 * Instance to be injected to mimic this class in tests
58 private static PyMoveModuleMembersDialog ourInstanceToReplace = null;
60 private final TopLevelSymbolsSelectionTable myMemberSelectionTable;
61 private final PyModuleMemberInfoModel myModuleMemberModel;
62 private final boolean mySeveralElementsSelected;
65 * Either creates new dialog or return singleton instance initialized with {@link #setInstanceToReplace)}.
66 * Singleton dialog is intended to be used in tests.
68 * @param project dialog project
69 * @param elements elements to move
71 *@param destination destination where elements have to be moved @return dialog
73 public static PyMoveModuleMembersDialog getInstance(@NotNull Project project,
74 @NotNull List<PsiNamedElement> elements,
75 @NotNull String source,
76 @NotNull String destination) {
77 return ourInstanceToReplace != null ? ourInstanceToReplace : new PyMoveModuleMembersDialog(project, elements, source, destination);
81 * Injects instance to be used in tests
83 * @param instanceToReplace instance to be used in tests
86 public static void setInstanceToReplace(@NotNull final PyMoveModuleMembersDialog instanceToReplace) {
87 ourInstanceToReplace = instanceToReplace;
91 * @param project dialog project
92 * @param elements elements to move
94 * @param destination destination where elements have to be moved
96 protected PyMoveModuleMembersDialog(@NotNull Project project,
97 @NotNull List<PsiNamedElement> elements,
98 @NotNull String source,
99 @NotNull String destination) {
100 super(project, source, destination);
102 assert !elements.isEmpty();
103 final PsiNamedElement firstElement = elements.get(0);
104 setTitle(PyBundle.message("refactoring.move.module.members.dialog.title"));
106 final PyFile pyFile = (PyFile)firstElement.getContainingFile();
107 myModuleMemberModel = new PyModuleMemberInfoModel(pyFile);
109 final List<PyModuleMemberInfo> symbolsInfos = collectModuleMemberInfos(myModuleMemberModel.myPyFile);
110 for (PyModuleMemberInfo info : symbolsInfos) {
111 //noinspection SuspiciousMethodCalls
112 info.setChecked(elements.contains(info.getMember()));
114 myModuleMemberModel.memberInfoChanged(new MemberInfoChange<>(symbolsInfos));
115 myMemberSelectionTable = new TopLevelSymbolsSelectionTable(symbolsInfos, myModuleMemberModel);
116 myMemberSelectionTable.addMemberInfoChangeListener(myModuleMemberModel);
117 myMemberSelectionTable.getModel().addTableModelListener(new TableModelListener() {
119 public void tableChanged(TableModelEvent e) {
123 mySeveralElementsSelected = elements.size() > 1;
124 final boolean tableIsVisible = mySeveralElementsSelected || PropertiesComponent.getInstance().getBoolean(BULK_MOVE_TABLE_VISIBLE);
125 final String description;
126 if (!tableIsVisible && elements.size() == 1) {
127 if (firstElement instanceof PyFunction) {
128 description = PyBundle.message("refactoring.move.module.members.dialog.description.function.$0", firstElement.getName());
130 else if (firstElement instanceof PyClass) {
131 description = PyBundle.message("refactoring.move.module.members.dialog.description.class.$0", firstElement.getName());
134 description = PyBundle.message("refactoring.move.module.members.dialog.description.variable.$0", firstElement.getName());
138 description = PyBundle.message("refactoring.move.module.members.dialog.description.selection");
140 myDescription.setText(description);
141 final HideableDecorator decorator = new HideableDecorator(myExtraPanel, PyBundle.message("refactoring.move.module.members.dialog.table.title"), true) {
143 protected void on() {
145 myDescription.setText(PyBundle.message("refactoring.move.module.members.dialog.description.selection"));
146 PropertiesComponent.getInstance().setValue(BULK_MOVE_TABLE_VISIBLE, true);
150 protected void off() {
152 PropertiesComponent.getInstance().setValue(BULK_MOVE_TABLE_VISIBLE, false);
155 decorator.setOn(tableIsVisible);
156 decorator.setContentComponent(new JBScrollPane(myMemberSelectionTable) {
158 public Dimension getMinimumSize() {
159 // Prevent growth of the dialog after several expand/collapse actions
160 return new Dimension((int)super.getMinimumSize().getWidth(), 0);
168 protected void doWhenFirstShown() {
169 super.doWhenFirstShown();
170 enlargeDialogHeightIfNecessary();
173 private void enlargeDialogHeightIfNecessary() {
174 if (mySeveralElementsSelected && !PropertiesComponent.getInstance(getProject()).getBoolean(BULK_MOVE_TABLE_VISIBLE)) {
175 final DialogWrapperPeer peer = getPeer();
176 final Dimension realSize = peer.getSize();
177 final double preferredHeight = peer.getPreferredSize().getHeight();
178 if (realSize.getHeight() < preferredHeight) {
179 peer.setSize((int)realSize.getWidth(), (int)preferredHeight);
186 protected String getDimensionServiceKey() {
187 return "#com.jetbrains.python.refactoring.move.PyMoveModuleMembersDialog";
191 protected String getHelpId() {
192 return "python.reference.moveModuleMembers";
196 protected boolean areButtonsValid() {
197 return !myMemberSelectionTable.getSelectedMemberInfos().isEmpty();
201 * @return selected elements in the same order as they are declared in the original file
204 public List<PyElement> getSelectedTopLevelSymbols() {
205 final Collection<PyModuleMemberInfo> selectedMembers = myMemberSelectionTable.getSelectedMemberInfos();
206 final List<PyElement> selectedElements = ContainerUtil.map(selectedMembers, info -> info.getMember());
207 return ContainerUtil.sorted(selectedElements, (e1, e2) -> PyPsiUtils.isBefore(e1, e2) ? -1 : 1);
211 private static List<PyModuleMemberInfo> collectModuleMemberInfos(@NotNull PyFile pyFile) {
212 final List<PyElement> moduleMembers = PyMoveModuleMembersHelper.getTopLevelModuleMembers(pyFile);
213 return ContainerUtil.mapNotNull(moduleMembers, element -> new PyModuleMemberInfo(element));
216 static class TopLevelSymbolsSelectionTable extends AbstractMemberSelectionTable<PyElement, PyModuleMemberInfo> {
217 public TopLevelSymbolsSelectionTable(Collection<PyModuleMemberInfo> memberInfos,
218 @Nullable MemberInfoModel<PyElement, PyModuleMemberInfo> memberInfoModel) {
219 super(memberInfos, memberInfoModel, null);
224 protected Object getAbstractColumnValue(PyModuleMemberInfo memberInfo) {
229 protected boolean isAbstractColumnEditable(int rowIndex) {
234 protected void setVisibilityIcon(PyModuleMemberInfo memberInfo, RowIcon icon) {
239 protected Icon getOverrideIcon(PyModuleMemberInfo memberInfo) {