force running "main" tools in global context
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / RecentProjectsManagerBase.java
1 /*
2  * Copyright 2000-2012 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.intellij.ide;
17
18 import com.intellij.openapi.actionSystem.AnAction;
19 import com.intellij.openapi.actionSystem.AnActionEvent;
20 import com.intellij.openapi.actionSystem.PlatformDataKeys;
21 import com.intellij.openapi.actionSystem.Separator;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.components.PersistentStateComponent;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.project.ProjectManager;
26 import com.intellij.openapi.project.ProjectManagerAdapter;
27 import com.intellij.openapi.project.ProjectUtil;
28 import com.intellij.openapi.ui.Messages;
29 import com.intellij.openapi.util.Ref;
30 import com.intellij.openapi.util.SystemInfo;
31 import com.intellij.openapi.util.io.FileUtil;
32 import com.intellij.openapi.util.registry.Registry;
33 import com.intellij.openapi.util.text.StringUtil;
34 import com.intellij.openapi.wm.IdeFrame;
35 import com.intellij.openapi.wm.WelcomeScreen;
36 import com.intellij.openapi.wm.impl.IdeRootPane;
37 import com.intellij.openapi.wm.impl.welcomeScreen.DefaultWelcomeScreen;
38 import com.intellij.util.containers.ContainerUtil;
39 import com.intellij.util.messages.MessageBus;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42
43 import java.io.*;
44 import java.util.*;
45
46 /**
47  * @author yole
48  */
49 public abstract class RecentProjectsManagerBase implements PersistentStateComponent<RecentProjectsManagerBase.State> {
50   public static RecentProjectsManagerBase getInstance() {
51     return ApplicationManager.getApplication().getComponent(RecentProjectsManagerBase.class);
52   }
53
54   public static class State {
55     public List<String> recentPaths = new ArrayList<String>();
56     public List<String> openPaths = new ArrayList<String>();
57     public Map<String, String> names = new HashMap<String, String>();
58     public String lastPath;
59   }
60
61   private State myState = new State();
62
63   public RecentProjectsManagerBase(ProjectManager projectManager, MessageBus messageBus) {
64     projectManager.addProjectManagerListener(new MyProjectManagerListener());
65     messageBus.connect().subscribe(AppLifecycleListener.TOPIC, new MyAppLifecycleListener());
66   }
67
68   public State getState() {
69     validateRecentProjects();
70     return myState;
71   }
72
73   public void loadState(final State state) {
74     myState = state;
75     if (myState.lastPath != null && !new File(myState.lastPath).exists()) {
76       myState.lastPath = null;
77     }
78     if (myState.lastPath != null) {
79       File lastFile = new File(myState.lastPath);
80       if (lastFile.isDirectory() && !new File(lastFile, ProjectUtil.DIRECTORY_BASED_PROJECT_DIR).exists()) {
81         myState.lastPath = null;
82       }
83     }
84   }
85
86   private void validateRecentProjects() {
87     synchronized (myState) {
88       for (Iterator i = myState.recentPaths.iterator(); i.hasNext();) {
89         String s = (String)i.next();
90
91         if (s == null) {
92           i.remove();
93         }
94       }
95       while (myState.recentPaths.size() > Registry.intValue("ide.max.recent.projects")) {
96         final int index = myState.recentPaths.size() - 1;
97         myState.names.remove(myState.recentPaths.get(index));
98         myState.recentPaths.remove(index);
99       }
100     }
101   }
102
103   public void removePath(final String path) {
104     if (path == null) return;
105     if (SystemInfo.isFileSystemCaseSensitive) {
106       myState.recentPaths.remove(path);
107       myState.names.remove(path);
108     }
109     else {
110       Iterator<String> i = myState.recentPaths.iterator();
111       while (i.hasNext()) {
112         String p = i.next();
113         if (path.equalsIgnoreCase(p)) {
114           myState.names.remove(p);
115           i.remove();
116         }
117       }
118     }
119   }
120
121   public String getLastProjectPath() {
122     return myState.lastPath;
123   }
124
125   public void updateLastProjectPath() {
126     Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
127     if (openProjects.length == 0) {
128       myState.lastPath = null;
129       myState.openPaths = Collections.emptyList();
130     } else {
131       myState.lastPath = getProjectPath(openProjects[openProjects.length - 1]);
132       myState.openPaths = new ArrayList<String>();
133       for (Project openProject : openProjects) {
134         final String path = getProjectPath(openProject);
135         ContainerUtil.addIfNotNull(myState.openPaths, path);
136         myState.names.put(path, getProjectDisplayName(openProject));
137       }
138     }
139   }
140
141   @NotNull
142   protected String getProjectDisplayName(Project project) {
143     return "";
144   }
145   
146
147   /**
148    * @param addClearListItem - used for detecting whether the "Clear List" action should be added
149    * to the end of the returned list of actions
150    * @return
151    */
152   public AnAction[] getRecentProjectsActions(boolean addClearListItem) {
153     validateRecentProjects();
154
155     Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
156
157     ArrayList<AnAction> actions = new ArrayList<AnAction>();
158     final Map<String, Integer> map = new LinkedHashMap<String, Integer>();
159     final List<String> paths = new ArrayList<String>();
160     synchronized (myState) {
161       outer: for (String recentPath : myState.recentPaths) {
162         if (recentPath == null) {
163           continue;
164         }
165
166         for (Project openProject : openProjects) {
167           final String path = getProjectPath(openProject);
168           if (path == null || recentPath.equals(path)) {
169             continue outer;
170           }
171         }
172
173         final String projectName = getProjectName(recentPath);
174         map.put(projectName, map.containsKey(projectName) ? map.get(projectName) + 1 : 1);
175         paths.add(recentPath);
176       }
177     }
178
179     for (final String path : paths) {
180       final String projectName = getProjectName(path);
181       boolean needShowPath = false;
182       String displayName = myState.names.get(path);
183       if (StringUtil.isEmptyOrSpaces(displayName)) {
184         if (map.get(projectName) > 1) {
185           displayName = path;
186         }
187         else {
188           displayName = projectName;
189           needShowPath = true;
190         }
191       }
192       actions.add(new ReopenProjectAction(path, displayName, needShowPath));
193     }
194
195     if (actions.isEmpty()) {
196       return AnAction.EMPTY_ARRAY;
197     }
198
199     ArrayList<AnAction> list = new ArrayList<AnAction>();
200     for (AnAction action : actions) {
201       list.add(action);
202     }
203     if (addClearListItem) {
204       AnAction clearListAction = new AnAction(IdeBundle.message("action.clear.list")) {
205         public void actionPerformed(AnActionEvent e) {
206           final int rc = Messages.showOkCancelDialog(e.getData(PlatformDataKeys.PROJECT),
207                                                      "Would you like to clear the list of recent projects?",
208                                                      "Clear Recent Projects List",
209                                                      Messages.getQuestionIcon());
210
211           if (rc == 0) {
212             synchronized (myState) {
213               myState.recentPaths.clear();
214             }
215             IdeFrame frame = e.getData(IdeFrame.KEY);
216             if (frame != null) {
217               IdeRootPane rootPane = (IdeRootPane) frame.getComponent();
218               final WelcomeScreen welcomeScreen = rootPane.getWelcomeScreen();
219               if (welcomeScreen instanceof DefaultWelcomeScreen) {
220                 ((DefaultWelcomeScreen)welcomeScreen).hideRecentProjectsPanel();
221               }
222             }
223           }
224         }
225       };
226       
227       list.add(Separator.getInstance());
228       list.add(clearListAction);
229     }
230
231     return list.toArray(new AnAction[list.size()]);
232   }
233
234   private void markPathRecent(String path) {
235     synchronized (myState) {
236       myState.lastPath = path;
237       removePath(path);
238       myState.recentPaths.add(0, path);
239     }
240   }
241
242   @Nullable
243   protected abstract String getProjectPath(Project project);
244
245   protected abstract void doOpenProject(String projectPath, Project projectToClose, final boolean forceOpenInNewFrame);
246
247   public static boolean isValidProjectPath(String projectPath) {
248     final File file = new File(projectPath);
249     return file.exists() && (!file.isDirectory() || new File(file, ProjectUtil.DIRECTORY_BASED_PROJECT_DIR).exists());
250   }
251
252   private class MyProjectManagerListener extends ProjectManagerAdapter {
253     public void projectOpened(final Project project) {
254       String path = getProjectPath(project);
255       if (path != null) {
256         markPathRecent(path);
257       }
258     }
259
260     @Override
261     public void projectClosing(Project project) {
262       myState.names.put(getProjectPath(project), getProjectDisplayName(project));
263     }
264
265     public void projectClosed(final Project project) {
266       Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
267       if (openProjects.length > 0) {
268         String path = getProjectPath(openProjects[openProjects.length - 1]);
269         if (path != null) {
270           markPathRecent(path);
271         }
272       }
273     }
274   }
275
276   @NotNull
277   private static String getProjectName(String path) {
278     final File file = new File(path);
279     if (file.isDirectory()) {
280       final File nameFile = new File(new File(path, Project.DIRECTORY_STORE_FOLDER), ".name");
281       if (nameFile.exists()) {
282         BufferedReader in = null;
283         try {
284           in = new BufferedReader(new InputStreamReader(new FileInputStream(nameFile), "UTF-8"));
285           final String name = in.readLine();
286           if (name != null && name.length() > 0) return name.trim();
287         }
288         catch (IOException e) {
289           // ignore
290         }
291         finally {
292           if (in != null) {
293             try {
294               in.close();
295             }
296             catch (IOException e) {
297               // ignore
298             }
299           }
300         }
301       }
302       return file.getName();
303     }
304     else {
305       return FileUtil.getNameWithoutExtension(file.getName());
306     }
307   }
308
309   private class MyAppLifecycleListener extends AppLifecycleListener.Adapter {
310     public void appFrameCreated(final String[] commandLineArgs, @NotNull final Ref<Boolean> willOpenProject) {
311       if (GeneralSettings.getInstance().isReopenLastProject() && getLastProjectPath() != null) {
312         willOpenProject.set(Boolean.TRUE);
313       }
314     }
315
316     public void appStarting(final Project projectFromCommandLine) {
317       if (projectFromCommandLine != null) return;
318       GeneralSettings generalSettings = GeneralSettings.getInstance();
319       if (generalSettings.isReopenLastProject()) {
320         List<String> openPaths = myState.openPaths;
321         if (!openPaths.isEmpty()) {
322           for (String openPath : openPaths) {
323             if (isValidProjectPath(openPath)) doOpenProject(openPath, null, true);
324           }
325         }
326         else {
327           String lastProjectPath = getLastProjectPath();
328           if (lastProjectPath != null) {
329             if (isValidProjectPath(lastProjectPath)) doOpenProject(lastProjectPath, null, false);
330           }
331         }
332       }
333     }
334
335     public void projectFrameClosed() {
336       updateLastProjectPath();
337     }
338
339     public void projectOpenFailed() {
340       updateLastProjectPath();
341     }
342
343     public void appClosing() {
344       updateLastProjectPath();
345     }
346   }
347 }