2 * Copyright 2000-2012 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.intellij.ide;
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;
49 public abstract class RecentProjectsManagerBase implements PersistentStateComponent<RecentProjectsManagerBase.State> {
50 public static RecentProjectsManagerBase getInstance() {
51 return ApplicationManager.getApplication().getComponent(RecentProjectsManagerBase.class);
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;
61 private State myState = new State();
63 public RecentProjectsManagerBase(ProjectManager projectManager, MessageBus messageBus) {
64 projectManager.addProjectManagerListener(new MyProjectManagerListener());
65 messageBus.connect().subscribe(AppLifecycleListener.TOPIC, new MyAppLifecycleListener());
68 public State getState() {
69 validateRecentProjects();
73 public void loadState(final State state) {
75 if (myState.lastPath != null && !new File(myState.lastPath).exists()) {
76 myState.lastPath = null;
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;
86 private void validateRecentProjects() {
87 synchronized (myState) {
88 for (Iterator i = myState.recentPaths.iterator(); i.hasNext();) {
89 String s = (String)i.next();
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);
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);
110 Iterator<String> i = myState.recentPaths.iterator();
111 while (i.hasNext()) {
113 if (path.equalsIgnoreCase(p)) {
114 myState.names.remove(p);
121 public String getLastProjectPath() {
122 return myState.lastPath;
125 public void updateLastProjectPath() {
126 Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
127 if (openProjects.length == 0) {
128 myState.lastPath = null;
129 myState.openPaths = Collections.emptyList();
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));
142 protected String getProjectDisplayName(Project project) {
148 * @param addClearListItem - used for detecting whether the "Clear List" action should be added
149 * to the end of the returned list of actions
152 public AnAction[] getRecentProjectsActions(boolean addClearListItem) {
153 validateRecentProjects();
155 Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
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) {
166 for (Project openProject : openProjects) {
167 final String path = getProjectPath(openProject);
168 if (path == null || recentPath.equals(path)) {
173 final String projectName = getProjectName(recentPath);
174 map.put(projectName, map.containsKey(projectName) ? map.get(projectName) + 1 : 1);
175 paths.add(recentPath);
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) {
188 displayName = projectName;
192 actions.add(new ReopenProjectAction(path, displayName, needShowPath));
195 if (actions.isEmpty()) {
196 return AnAction.EMPTY_ARRAY;
199 ArrayList<AnAction> list = new ArrayList<AnAction>();
200 for (AnAction action : actions) {
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());
212 synchronized (myState) {
213 myState.recentPaths.clear();
215 IdeFrame frame = e.getData(IdeFrame.KEY);
217 IdeRootPane rootPane = (IdeRootPane) frame.getComponent();
218 final WelcomeScreen welcomeScreen = rootPane.getWelcomeScreen();
219 if (welcomeScreen instanceof DefaultWelcomeScreen) {
220 ((DefaultWelcomeScreen)welcomeScreen).hideRecentProjectsPanel();
227 list.add(Separator.getInstance());
228 list.add(clearListAction);
231 return list.toArray(new AnAction[list.size()]);
234 private void markPathRecent(String path) {
235 synchronized (myState) {
236 myState.lastPath = path;
238 myState.recentPaths.add(0, path);
243 protected abstract String getProjectPath(Project project);
245 protected abstract void doOpenProject(String projectPath, Project projectToClose, final boolean forceOpenInNewFrame);
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());
252 private class MyProjectManagerListener extends ProjectManagerAdapter {
253 public void projectOpened(final Project project) {
254 String path = getProjectPath(project);
256 markPathRecent(path);
261 public void projectClosing(Project project) {
262 myState.names.put(getProjectPath(project), getProjectDisplayName(project));
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]);
270 markPathRecent(path);
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;
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();
288 catch (IOException e) {
296 catch (IOException e) {
302 return file.getName();
305 return FileUtil.getNameWithoutExtension(file.getName());
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);
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);
327 String lastProjectPath = getLastProjectPath();
328 if (lastProjectPath != null) {
329 if (isValidProjectPath(lastProjectPath)) doOpenProject(lastProjectPath, null, false);
335 public void projectFrameClosed() {
336 updateLastProjectPath();
339 public void projectOpenFailed() {
340 updateLastProjectPath();
343 public void appClosing() {
344 updateLastProjectPath();