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