EDU-509 Return back Code menu
[idea/community.git] / python / educational-python / src / com / jetbrains / python / edu / PyCharmEduInitialConfigurator.java
1 /*
2  * Copyright 2000-2013 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.edu;
17
18 import com.google.common.collect.Sets;
19 import com.intellij.codeInsight.CodeInsightSettings;
20 import com.intellij.codeInsight.intention.IntentionActionBean;
21 import com.intellij.codeInsight.intention.IntentionManager;
22 import com.intellij.execution.Executor;
23 import com.intellij.execution.ExecutorRegistryImpl;
24 import com.intellij.execution.executors.DefaultDebugExecutor;
25 import com.intellij.ide.AppLifecycleListener;
26 import com.intellij.ide.GeneralSettings;
27 import com.intellij.ide.SelectInTarget;
28 import com.intellij.ide.projectView.impl.AbstractProjectViewPane;
29 import com.intellij.ide.scopeView.ScopeViewPane;
30 import com.intellij.ide.ui.UISettings;
31 import com.intellij.ide.ui.customization.ActionUrl;
32 import com.intellij.ide.ui.customization.CustomActionsSchema;
33 import com.intellij.ide.ui.customization.CustomizationUtil;
34 import com.intellij.ide.util.PropertiesComponent;
35 import com.intellij.ide.util.TipAndTrickBean;
36 import com.intellij.notification.EventLog;
37 import com.intellij.openapi.actionSystem.ActionManager;
38 import com.intellij.openapi.actionSystem.AnAction;
39 import com.intellij.openapi.actionSystem.DefaultActionGroup;
40 import com.intellij.openapi.application.ApplicationManager;
41 import com.intellij.openapi.editor.colors.EditorColorsManager;
42 import com.intellij.openapi.editor.colors.EditorColorsScheme;
43 import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
44 import com.intellij.openapi.extensions.Extensions;
45 import com.intellij.openapi.extensions.ExtensionsArea;
46 import com.intellij.openapi.fileChooser.impl.FileChooserUtil;
47 import com.intellij.openapi.fileTypes.FileTypeManager;
48 import com.intellij.openapi.keymap.Keymap;
49 import com.intellij.openapi.keymap.ex.KeymapManagerEx;
50 import com.intellij.openapi.keymap.impl.KeymapImpl;
51 import com.intellij.openapi.keymap.impl.ui.Group;
52 import com.intellij.openapi.project.DumbAwareRunnable;
53 import com.intellij.openapi.project.Project;
54 import com.intellij.openapi.project.ProjectManager;
55 import com.intellij.openapi.project.ProjectManagerAdapter;
56 import com.intellij.openapi.project.ex.ProjectManagerEx;
57 import com.intellij.openapi.startup.StartupManager;
58 import com.intellij.openapi.util.Disposer;
59 import com.intellij.openapi.util.Key;
60 import com.intellij.openapi.vfs.VfsUtil;
61 import com.intellij.openapi.vfs.VirtualFile;
62 import com.intellij.openapi.wm.*;
63 import com.intellij.platform.DirectoryProjectConfigurator;
64 import com.intellij.platform.PlatformProjectViewOpener;
65 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
66 import com.intellij.psi.PsiDirectory;
67 import com.intellij.psi.PsiManager;
68 import com.intellij.psi.codeStyle.CodeStyleSettings;
69 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
70 import com.intellij.ui.treeStructure.Tree;
71 import com.intellij.util.containers.ContainerUtil;
72 import com.intellij.util.messages.MessageBus;
73 import com.intellij.util.ui.tree.TreeUtil;
74 import com.jetbrains.python.PythonLanguage;
75 import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
76 import com.jetbrains.python.inspections.PyPep8Inspection;
77 import org.jetbrains.annotations.NonNls;
78 import org.jetbrains.annotations.NotNull;
79 import org.jetbrains.annotations.Nullable;
80
81 import javax.swing.*;
82 import javax.swing.tree.DefaultMutableTreeNode;
83 import javax.swing.tree.DefaultTreeModel;
84 import javax.swing.tree.TreeNode;
85 import javax.swing.tree.TreePath;
86 import java.util.Collections;
87 import java.util.HashSet;
88 import java.util.Set;
89
90 /**
91  * @author traff
92  */
93 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "UtilityClassWithPublicConstructor"})
94 public class PyCharmEduInitialConfigurator {
95   @NonNls private static final String DISPLAYED_PROPERTY = "PyCharmEDU.initialConfigurationShown";
96
97   @NonNls private static final String CONFIGURED = "PyCharmEDU.InitialConfiguration";
98   @NonNls private static final String CONFIGURED_V1 = "PyCharmEDU.InitialConfiguration.V1";
99   @NonNls private static final String CONFIGURED_V2 = "PyCharmEDU.InitialConfiguration.V2";
100
101   private static final Set<String> UNRELATED_TIPS = Sets.newHashSet("LiveTemplatesDjango.html", "TerminalOpen.html",
102                                                                     "Terminal.html", "ConfiguringTerminal.html");
103   private static final Set<String> HIDDEN_ACTIONS = ContainerUtil.newHashSet("CopyAsPlainText", "CopyAsRichText", "EditorPasteSimple",
104                                                                              "Folding", "Generate", "CompareClipboardWithSelection",
105                                                                              "ChangeFileEncodingAction", "CloseAllUnmodifiedEditors",
106                                                                              "CloseAllUnpinnedEditors", "CloseAllEditorsButActive",
107                                                                              "CopyReference", "MoveTabRight", "MoveTabDown", "External Tools",
108                                                                              "MoveEditorToOppositeTabGroup", "OpenEditorInOppositeTabGroup",
109                                                                              "ChangeSplitOrientation", "PinActiveTab", "Tabs Placement",
110                                                                              "TabsAlphabeticalMode", "AddNewTabToTheEndMode", "NextTab",
111                                                                              "PreviousTab", "Add to Favorites", "Add All To Favorites",
112                                                                              "ValidateXml", "NewHtmlFile", "CleanPyc", "Images.ShowThumbnails",
113                                                                              "CompareFileWithEditor", "SynchronizeCurrentFile",
114                                                                              "Mark Directory As", "CompareTwoFiles", "ShowFilePath",
115                                                                              "ChangesView.ApplyPatch", "TemplateProjectProperties",
116                                                                              "ExportToHTML", "SaveAll", "Export/Import Actions",
117                                                                              "Synchronize", "Line Separators", "ToggleReadOnlyAttribute",
118                                                                              "Macros", "EditorToggleCase", "EditorJoinLines", "FillParagraph",
119                                                                              "Convert Indents", "TemplateParametersNavigation", "EscapeEntities",
120                                                                              "QuickDefinition", "ExpressionTypeInfo", "EditorContextInfo",
121                                                                              "ShowErrorDescription", "RecentChanges", "CompareActions",
122                                                                              "GotoCustomRegion", "JumpToLastChange", "JumpToNextChange",
123                                                                              "SelectIn", "GotoTypeDeclaration", "QuickChangeScheme",
124                                                                              "GotoTest", "GotoRelated", "Hierarchy Actions", "Bookmarks",
125                                                                              "Goto Error/Bookmark Actions", "GoToEditPointGroup",
126                                                                              "Change Navigation Actions", "Method Navigation Actions",
127                                                                              "EvaluateExpression", "Pause", "ViewBreakpoints",
128                                                                              "XDebugger.MuteBreakpoints", "SaveAs");
129
130   public static class First {
131
132     public First() {
133       patchRootAreaExtensions();
134     }
135   }
136
137   /**
138    * @noinspection UnusedParameters
139    */
140   public PyCharmEduInitialConfigurator(MessageBus bus,
141                                        CodeInsightSettings codeInsightSettings,
142                                        final PropertiesComponent propertiesComponent,
143                                        FileTypeManager fileTypeManager,
144                                        final ProjectManagerEx projectManager) {
145     final UISettings uiSettings = UISettings.getInstance();
146     if (!propertiesComponent.getBoolean(CONFIGURED_V2)) {
147       EditorSettingsExternalizable editorSettings = EditorSettingsExternalizable.getInstance();
148       editorSettings.setEnsureNewLineAtEOF(true);
149       propertiesComponent.setValue(CONFIGURED_V2, true);
150     }
151     if (!propertiesComponent.getBoolean(CONFIGURED_V1)) {
152       patchMainMenu();
153       uiSettings.SHOW_NAVIGATION_BAR = false;
154       propertiesComponent.setValue(CONFIGURED_V1, true);
155       propertiesComponent.setValue("ShowDocumentationInToolWindow", true);
156     }
157
158     if (!propertiesComponent.getBoolean(CONFIGURED)) {
159       propertiesComponent.setValue(CONFIGURED, "true");
160       propertiesComponent.setValue("toolwindow.stripes.buttons.info.shown", "true");
161
162       uiSettings.HIDE_TOOL_STRIPES = false;
163       uiSettings.SHOW_MEMORY_INDICATOR = false;
164       uiSettings.SHOW_DIRECTORY_FOR_NON_UNIQUE_FILENAMES = true;
165       uiSettings.SHOW_MAIN_TOOLBAR = false;
166
167       codeInsightSettings.REFORMAT_ON_PASTE = CodeInsightSettings.NO_REFORMAT;
168
169       GeneralSettings.getInstance().setShowTipsOnStartup(false);
170
171       EditorSettingsExternalizable.getInstance().setVirtualSpace(false);
172       EditorSettingsExternalizable.getInstance().getOptions().ARE_LINE_NUMBERS_SHOWN = true;
173       final CodeStyleSettings settings = CodeStyleSettingsManager.getInstance().getCurrentSettings();
174       settings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true;
175       settings.getCommonSettings(PythonLanguage.getInstance()).ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true;
176       uiSettings.SHOW_DIRECTORY_FOR_NON_UNIQUE_FILENAMES = true;
177       uiSettings.SHOW_MEMORY_INDICATOR = false;
178       final String ignoredFilesList = fileTypeManager.getIgnoredFilesList();
179       ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> FileTypeManager.getInstance().setIgnoredFilesList(ignoredFilesList + ";*$py.class")));
180       PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP = false;
181     }
182     final EditorColorsScheme editorColorsScheme = EditorColorsManager.getInstance().getScheme(EditorColorsScheme.DEFAULT_SCHEME_NAME);
183     editorColorsScheme.setEditorFontSize(14);
184
185     if (!propertiesComponent.isValueSet(DISPLAYED_PROPERTY)) {
186
187       bus.connect().subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener.Adapter() {
188         @Override
189         public void welcomeScreenDisplayed() {
190
191           ApplicationManager.getApplication().invokeLater(() -> {
192             if (!propertiesComponent.isValueSet(DISPLAYED_PROPERTY)) {
193               GeneralSettings.getInstance().setShowTipsOnStartup(false);
194               propertiesComponent.setValue(DISPLAYED_PROPERTY, "true");
195
196               patchKeymap();
197             }
198           });
199         }
200       });
201     }
202
203     bus.connect().subscribe(ProjectManager.TOPIC, new ProjectManagerAdapter() {
204       @Override
205       public void projectOpened(final Project project) {
206         if (project.isDefault()) return;
207         if (FileChooserUtil.getLastOpenedFile(project) == null) {
208           FileChooserUtil.setLastOpenedFile(project, VfsUtil.getUserHomeDir());
209         }
210
211         patchProjectAreaExtensions(project);
212
213         StartupManager.getInstance(project).runWhenProjectIsInitialized(new DumbAwareRunnable() {
214           @Override
215           public void run() {
216             if (project.isDisposed()) return;
217             updateInspectionsProfile();
218             openProjectStructure();
219           }
220
221           private void openProjectStructure() {
222             ToolWindowManager.getInstance(project).invokeLater(new Runnable() {
223               int count = 0;
224
225               public void run() {
226                 if (project.isDisposed()) return;
227                 if (count++ < 3) { // we need to call this after ToolWindowManagerImpl.registerToolWindowsFromBeans
228                   ToolWindowManager.getInstance(project).invokeLater(this);
229                   return;
230                 }
231                 ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow("Project");
232                 if (toolWindow != null && toolWindow.getType() != ToolWindowType.SLIDING) {
233                   toolWindow.activate(null);
234                 }
235               }
236             });
237           }
238
239           private void updateInspectionsProfile() {
240             final String[] codes = new String[]{"W29", "E501"};
241             final VirtualFile baseDir = project.getBaseDir();
242             final PsiDirectory directory = PsiManager.getInstance(project).findDirectory(baseDir);
243             if (directory != null) {
244               InspectionProjectProfileManager.getInstance(project).getInspectionProfile().modifyToolSettings(
245                 Key.<PyPep8Inspection>create(PyPep8Inspection.INSPECTION_SHORT_NAME), directory,
246                 inspection -> Collections.addAll(inspection.ignoredErrors, codes)
247               );
248             }
249           }
250         });
251       }
252     });
253   }
254
255   private static void patchMainMenu() {
256     final CustomActionsSchema schema = new CustomActionsSchema();
257
258     final JTree actionsTree = new Tree();
259     Group rootGroup = new Group("root", null, null);
260     final DefaultMutableTreeNode root = new DefaultMutableTreeNode(rootGroup);
261     DefaultTreeModel model = new DefaultTreeModel(root);
262     actionsTree.setModel(model);
263
264     schema.fillActionGroups(root);
265     for (int i = 0; i < root.getChildCount(); i++) {
266       final DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)root.getChildAt(i);
267       if ("Main menu".equals(getItemId(treeNode))) {
268         hideActionFromMainMenu(root, schema, treeNode);
269       }
270       hideActions(schema, root, treeNode, HIDDEN_ACTIONS);
271     }
272     CustomActionsSchema.getInstance().copyFrom(schema);
273   }
274
275   private static void hideActionFromMainMenu(@NotNull final DefaultMutableTreeNode root,
276                                              @NotNull final CustomActionsSchema schema, DefaultMutableTreeNode mainMenu){
277     final HashSet<String> menuItems = ContainerUtil.newHashSet("Tools", "VCS", "Refactor", "Window", "Run");
278     hideActions(schema, root, mainMenu, menuItems);
279   }
280
281   private static void hideActions(@NotNull CustomActionsSchema schema, @NotNull DefaultMutableTreeNode root,
282                                   @NotNull final TreeNode actionGroup, Set<String> items) {
283     for(int i = 0; i < actionGroup.getChildCount(); i++){
284       final DefaultMutableTreeNode child = (DefaultMutableTreeNode)actionGroup.getChildAt(i);
285       final int childCount = child.getChildCount();
286       final String childId = getItemId(child);
287       if (childId != null && items.contains(childId)){
288         final TreePath treePath = TreeUtil.getPath(root, child);
289         final ActionUrl url = CustomizationUtil.getActionUrl(treePath, ActionUrl.DELETED);
290         schema.addAction(url);
291       }
292       else if (childCount > 0) {
293         hideActions(schema, child, child, items);
294       }
295     }
296   }
297
298   @Nullable
299   private static String getItemId(@NotNull final DefaultMutableTreeNode child) {
300     final Object userObject = child.getUserObject();
301     if (userObject instanceof String) return (String)userObject;
302     return userObject instanceof Group ? ((Group)userObject).getName() : null;
303   }
304
305   private static void patchRootAreaExtensions() {
306     ExtensionsArea rootArea = Extensions.getArea(null);
307
308     for (ToolWindowEP ep : Extensions.getExtensions(ToolWindowEP.EP_NAME)) {
309       if (ToolWindowId.FAVORITES_VIEW.equals(ep.id) || ToolWindowId.TODO_VIEW.equals(ep.id) || EventLog.LOG_TOOL_WINDOW_ID.equals(ep.id)
310           || ToolWindowId.STRUCTURE_VIEW.equals(ep.id)) {
311         rootArea.getExtensionPoint(ToolWindowEP.EP_NAME).unregisterExtension(ep);
312       }
313     }
314
315     for (DirectoryProjectConfigurator ep : Extensions.getExtensions(DirectoryProjectConfigurator.EP_NAME)) {
316       if (ep instanceof PlatformProjectViewOpener) {
317         rootArea.getExtensionPoint(DirectoryProjectConfigurator.EP_NAME).unregisterExtension(ep);
318       }
319     }
320
321     // unregister unrelated tips
322     for (TipAndTrickBean tip : Extensions.getExtensions(TipAndTrickBean.EP_NAME)) {
323       if (UNRELATED_TIPS.contains(tip.fileName)) {
324         rootArea.getExtensionPoint(TipAndTrickBean.EP_NAME).unregisterExtension(tip);
325       }
326     }
327
328     for (IntentionActionBean ep : Extensions.getExtensions(IntentionManager.EP_INTENTION_ACTIONS)) {
329       if ("org.intellij.lang.regexp.intention.CheckRegExpIntentionAction".equals(ep.className)) {
330         rootArea.getExtensionPoint(IntentionManager.EP_INTENTION_ACTIONS).unregisterExtension(ep);
331       }
332     }
333   }
334
335   private static void patchProjectAreaExtensions(@NotNull final Project project) {
336     Executor debugExecutor = DefaultDebugExecutor.getDebugExecutorInstance();
337     unregisterAction(debugExecutor.getId(), ExecutorRegistryImpl.RUNNERS_GROUP);
338     unregisterAction(debugExecutor.getContextActionId(), ExecutorRegistryImpl.RUN_CONTEXT_GROUP);
339
340     ExtensionsArea projectArea = Extensions.getArea(project);
341
342     for (SelectInTarget target : Extensions.getExtensions(SelectInTarget.EP_NAME, project)) {
343       if (ToolWindowId.FAVORITES_VIEW.equals(target.getToolWindowId()) ||
344           ToolWindowId.STRUCTURE_VIEW.equals(target.getToolWindowId())) {
345         projectArea.getExtensionPoint(SelectInTarget.EP_NAME).unregisterExtension(target);
346       }
347     }
348
349     for (AbstractProjectViewPane pane : Extensions.getExtensions(AbstractProjectViewPane.EP_NAME, project)) {
350       if (pane.getId().equals(ScopeViewPane.ID)) {
351         Disposer.dispose(pane);
352         projectArea.getExtensionPoint(AbstractProjectViewPane.EP_NAME).unregisterExtension(pane);
353       }
354     }
355   }
356
357   private static void unregisterAction(String actionId, String groupId) {
358     ActionManager actionManager = ActionManager.getInstance();
359     AnAction action = actionManager.getAction(actionId);
360     if (action != null) {
361       AnAction actionGroup = actionManager.getAction(groupId);
362       if (actionGroup != null && actionGroup instanceof DefaultActionGroup) {
363         ((DefaultActionGroup)actionGroup).remove(action);
364         actionManager.unregisterAction(actionId);
365       }
366     }
367   }
368
369   private static void patchKeymap() {
370     Set<String> droppedActions = ContainerUtil.newHashSet(
371       "AddToFavoritesPopup",
372       "DatabaseView.ImportDataSources",
373       "CompileDirty", "Compile",
374       // hidden
375       "AddNewFavoritesList", "EditFavorites", "RenameFavoritesList", "RemoveFavoritesList");
376     KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
377
378
379     for (Keymap keymap : keymapManager.getAllKeymaps()) {
380       if (keymap.canModify()) continue;
381
382       KeymapImpl keymapImpl = (KeymapImpl)keymap;
383
384       for (String id : keymapImpl.getOwnActionIds()) {
385         if (droppedActions.contains(id)) keymapImpl.clearOwnActionsId(id);
386       }
387     }
388   }
389
390 }