5a564f0ce7092b950294561822dbac14baefa133
[idea/community.git] / python / src / com / jetbrains / python / refactoring / move / moduleMembers / PyMoveModuleMembersDialog.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.jetbrains.python.refactoring.move.moduleMembers;
17
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 com.jetbrains.python.refactoring.move.PyBaseMoveDialog;
37 import com.jetbrains.python.refactoring.move.PyMoveRefactoringUtil;
38 import org.jetbrains.annotations.NonNls;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41
42 import javax.swing.*;
43 import javax.swing.event.TableModelEvent;
44 import javax.swing.event.TableModelListener;
45 import java.awt.*;
46 import java.util.Collection;
47 import java.util.Comparator;
48 import java.util.List;
49
50 /**
51  * @author Mikhail Golubev
52  */
53 public class PyMoveModuleMembersDialog extends PyBaseMoveDialog {
54   @NonNls private final static String BULK_MOVE_TABLE_VISIBLE = "python.move.module.members.dialog.show.table";
55
56   private final TopLevelSymbolsSelectionTable myMemberSelectionTable;
57   private final PyModuleMemberInfoModel myModuleMemberModel;
58   private final boolean mySeveralElementsSelected;
59
60   /**
61    * @param project dialog project
62    * @param elements elements to move
63    * @param source
64    * @param destination destination where elements have to be moved
65    */
66   public PyMoveModuleMembersDialog(@NotNull Project project,
67                                    @NotNull List<PsiNamedElement> elements,
68                                    @NotNull String source,
69                                    @NotNull String destination) {
70     super(project, source, destination);
71
72     assert !elements.isEmpty();
73     final PsiNamedElement firstElement = elements.get(0);
74     setTitle(PyBundle.message("refactoring.move.module.members.dialog.title"));
75
76     final PyFile pyFile = (PyFile)firstElement.getContainingFile();
77     myModuleMemberModel = new PyModuleMemberInfoModel(pyFile);
78
79     final List<PyModuleMemberInfo> symbolsInfos = collectModuleMemberInfos(myModuleMemberModel.myPyFile);
80     for (PyModuleMemberInfo info : symbolsInfos) {
81       //noinspection SuspiciousMethodCalls
82       info.setChecked(elements.contains(info.getMember()));
83     }
84     myModuleMemberModel.memberInfoChanged(new MemberInfoChange<>(symbolsInfos));
85     myMemberSelectionTable = new TopLevelSymbolsSelectionTable(symbolsInfos, myModuleMemberModel);
86     myMemberSelectionTable.addMemberInfoChangeListener(myModuleMemberModel);
87     myMemberSelectionTable.getModel().addTableModelListener(new TableModelListener() {
88       @Override
89       public void tableChanged(TableModelEvent e) {
90         validateButtons();
91       }
92     });
93     mySeveralElementsSelected = elements.size() > 1;
94     final boolean tableIsVisible = mySeveralElementsSelected || PropertiesComponent.getInstance().getBoolean(BULK_MOVE_TABLE_VISIBLE);
95     final String description;
96     if (!tableIsVisible && elements.size() == 1) {
97       final String name = PyMoveRefactoringUtil.getCompactPresentableName(firstElement);
98       if (firstElement instanceof PyFunction) {
99         description =  PyBundle.message("refactoring.move.module.members.dialog.description.function.$0", name);
100       }
101       else if (firstElement instanceof PyClass) {
102         description = PyBundle.message("refactoring.move.module.members.dialog.description.class.$0", name);
103       }
104       else {
105         description = PyBundle.message("refactoring.move.module.members.dialog.description.variable.$0", name);
106       }
107     }
108     else {
109       description = PyBundle.message("refactoring.move.module.members.dialog.description.selection");
110     }
111     myDescription.setText(description);
112     final HideableDecorator decorator = new HideableDecorator(myExtraPanel, PyBundle.message("refactoring.move.module.members.dialog.table.title"), true) {
113       @Override
114       protected void on() {
115         super.on();
116         myDescription.setText(PyBundle.message("refactoring.move.module.members.dialog.description.selection"));
117         PropertiesComponent.getInstance().setValue(BULK_MOVE_TABLE_VISIBLE, true);
118       }
119
120       @Override
121       protected void off() {
122         super.off();
123         PropertiesComponent.getInstance().setValue(BULK_MOVE_TABLE_VISIBLE, false);
124       }
125     };
126     decorator.setOn(tableIsVisible);
127     decorator.setContentComponent(new JBScrollPane(myMemberSelectionTable) {
128       @Override
129       public Dimension getMinimumSize() {
130         // Prevent growth of the dialog after several expand/collapse actions
131         return new Dimension((int)super.getMinimumSize().getWidth(), 0);
132       }
133     });
134
135     init();
136   }
137
138   @Override
139   protected void doWhenFirstShown() {
140     super.doWhenFirstShown();
141     enlargeDialogHeightIfNecessary();
142   }
143   
144   private void enlargeDialogHeightIfNecessary() {
145     if (mySeveralElementsSelected && !PropertiesComponent.getInstance(getProject()).getBoolean(BULK_MOVE_TABLE_VISIBLE)) {
146       final DialogWrapperPeer peer = getPeer();
147       final Dimension realSize = peer.getSize();
148       final double preferredHeight = peer.getPreferredSize().getHeight();
149       if (realSize.getHeight() < preferredHeight) {
150         peer.setSize((int)realSize.getWidth(), (int)preferredHeight);
151       }
152     }
153   }
154
155   @Nullable
156   @Override
157   protected String getDimensionServiceKey() {
158     return "#com.jetbrains.python.refactoring.move.PyMoveModuleMembersDialog";
159   }
160
161   @Override
162   protected String getHelpId() {
163     return "python.reference.moveModuleMembers";
164   }
165
166   @Override
167   protected boolean areButtonsValid() {
168     return !myMemberSelectionTable.getSelectedMemberInfos().isEmpty();
169   }
170
171   /**
172    * @return selected elements in the same order as they are declared in the original file
173    */
174   @NotNull
175   public List<PyElement> getSelectedTopLevelSymbols() {
176     final Collection<PyModuleMemberInfo> selectedMembers = myMemberSelectionTable.getSelectedMemberInfos();
177     final List<PyElement> selectedElements = ContainerUtil.map(selectedMembers, info -> info.getMember());
178     return ContainerUtil.sorted(selectedElements, (e1, e2) -> PyPsiUtils.isBefore(e1, e2) ? -1 : 1);
179   }
180
181   @NotNull
182   private static List<PyModuleMemberInfo> collectModuleMemberInfos(@NotNull PyFile pyFile) {
183     final List<PyElement> moduleMembers = PyMoveModuleMembersHelper.getTopLevelModuleMembers(pyFile);
184     return ContainerUtil.mapNotNull(moduleMembers, element -> new PyModuleMemberInfo(element));
185   }
186
187   static class TopLevelSymbolsSelectionTable extends AbstractMemberSelectionTable<PyElement, PyModuleMemberInfo> {
188     public TopLevelSymbolsSelectionTable(Collection<PyModuleMemberInfo> memberInfos,
189                                          @Nullable MemberInfoModel<PyElement, PyModuleMemberInfo> memberInfoModel) {
190       super(memberInfos, memberInfoModel, null);
191     }
192
193     @Nullable
194     @Override
195     protected Object getAbstractColumnValue(PyModuleMemberInfo memberInfo) {
196       return null;
197     }
198
199     @Override
200     protected boolean isAbstractColumnEditable(int rowIndex) {
201       return false;
202     }
203
204     @Override
205     protected void setVisibilityIcon(PyModuleMemberInfo memberInfo, RowIcon icon) {
206
207     }
208
209     @Override
210     protected Icon getOverrideIcon(PyModuleMemberInfo memberInfo) {
211       return null;
212     }
213   }
214 }