001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.project.ui.actions;
043:
044: import java.awt.event.ActionEvent;
045: import java.awt.event.ActionListener;
046: import java.beans.PropertyChangeEvent;
047: import java.beans.PropertyChangeListener;
048: import java.io.IOException;
049: import java.net.URL;
050: import java.util.Iterator;
051: import java.util.List;
052: import javax.swing.AbstractAction;
053: import javax.swing.Action;
054: import javax.swing.ImageIcon;
055: import javax.swing.JComponent;
056: import javax.swing.JMenu;
057: import javax.swing.JMenuItem;
058: import javax.swing.event.ChangeEvent;
059: import javax.swing.event.ChangeListener;
060: import javax.swing.event.PopupMenuEvent;
061: import javax.swing.event.PopupMenuListener;
062: import org.netbeans.api.project.Project;
063: import org.netbeans.api.project.ProjectUtils;
064: import org.netbeans.modules.project.ui.OpenProjectList;
065: import org.netbeans.api.project.ProjectInformation;
066: import org.netbeans.api.project.ProjectManager;
067: import org.netbeans.modules.project.ui.ProjectTab;
068: import org.netbeans.modules.project.ui.api.UnloadedProjectInformation;
069: import org.openide.filesystems.FileStateInvalidException;
070: import org.openide.nodes.Node;
071: import org.openide.util.NbBundle;
072: import org.openide.util.Utilities;
073: import org.openide.awt.DynamicMenuContent;
074: import org.openide.util.WeakListeners;
075: import org.openide.util.actions.Presenter;
076: import org.openide.filesystems.FileChangeAdapter;
077: import org.openide.filesystems.FileEvent;
078: import org.openide.filesystems.FileObject;
079: import org.openide.filesystems.URLMapper;
080:
081: public class RecentProjects extends AbstractAction implements
082: Presenter.Menu, Presenter.Popup, PropertyChangeListener {
083:
084: /** Key for remembering project in JMenuItem
085: */
086: private static final String PROJECT_URL_KEY = "org.netbeans.modules.project.ui.RecentProjectItem.Project_URL"; // NOI18N
087: private final ProjectDirListener prjDirListener = new ProjectDirListener();
088:
089: private UpdatingMenu subMenu;
090:
091: private boolean recreate;
092:
093: public RecentProjects() {
094: super (NbBundle.getMessage(RecentProjects.class,
095: "LBL_RecentProjectsAction_Name")); // NOI18N
096: OpenProjectList.getDefault().addPropertyChangeListener(
097: WeakListeners.propertyChange(this , OpenProjectList
098: .getDefault()));
099: recreate = true;
100: }
101:
102: public boolean isEnabled() {
103: return !OpenProjectList.getDefault().isRecentProjectsEmpty();
104: }
105:
106: /** Perform the action. Tries the performer and then scans the ActionMap
107: * of selected topcomponent.
108: */
109: public void actionPerformed(ActionEvent ev) {
110: // no operation
111: }
112:
113: public JMenuItem getMenuPresenter() {
114: createMainSubMenu();
115: return subMenu;
116: }
117:
118: public JMenuItem getPopupPresenter() {
119: JMenu menu = createSubMenu();
120: fillSubMenu(menu);
121: return menu;
122: }
123:
124: private UpdatingMenu createSubMenu() {
125: UpdatingMenu menu = new UpdatingMenu(this );
126: //ok to have mnenomics here, not shown on mac anyway
127: menu.setMnemonic(NbBundle.getMessage(RecentProjects.class,
128: "MNE_RecentProjectsAction_Name").charAt(0));
129: return menu;
130: }
131:
132: private void createMainSubMenu() {
133: if (subMenu == null) {
134: subMenu = createSubMenu();
135: // model listening is the only lazy menu procedure that works on macosx
136: subMenu.getModel().addChangeListener(subMenu);
137:
138: }
139: }
140:
141: private void fillSubMenu(JMenu menu) {
142: menu.removeAll();
143:
144: List<UnloadedProjectInformation> projects = OpenProjectList
145: .getDefault().getRecentProjectsInformation();
146: if (projects.isEmpty()) {
147: menu.setEnabled(false);
148: return;
149: }
150:
151: menu.setEnabled(true);
152: ActionListener jmiActionListener = new MenuItemActionListener();
153:
154: // Fill menu with items
155:
156: for (UnloadedProjectInformation p : projects) {
157: URL prjDirURL = p.getURL();
158: FileObject prjDir = URLMapper.findFileObject(prjDirURL);
159: if (prjDirURL == null || prjDir == null
160: || !prjDir.isValid()) {
161: continue;
162: }
163: prjDir.removeFileChangeListener(prjDirListener);
164: prjDir.addFileChangeListener(prjDirListener);
165: JMenuItem jmi = new JMenuItem(p.getDisplayName(), p
166: .getIcon());
167: menu.add(jmi);
168: jmi.putClientProperty(PROJECT_URL_KEY, prjDirURL);
169: jmi.addActionListener(jmiActionListener);
170: }
171: }
172:
173: // Implementation of change listener ---------------------------------------
174:
175: public void propertyChange(PropertyChangeEvent e) {
176:
177: if (OpenProjectList.PROPERTY_RECENT_PROJECTS.equals(e
178: .getPropertyName())) {
179: createMainSubMenu();
180: subMenu.setEnabled(!OpenProjectList.getDefault()
181: .isRecentProjectsEmpty());
182: recreate = true;
183: }
184:
185: }
186:
187: // Innerclasses ------------------------------------------------------------
188:
189: private static class MenuItemActionListener implements
190: ActionListener {
191:
192: public void actionPerformed(ActionEvent e) {
193:
194: if (e.getSource() instanceof JMenuItem) {
195: JMenuItem jmi = (JMenuItem) e.getSource();
196:
197: URL url = (URL) jmi.getClientProperty(PROJECT_URL_KEY);
198: Project project = null;
199:
200: FileObject dir = URLMapper.findFileObject(url);
201: if (dir != null && dir.isFolder()) {
202: try {
203: project = ProjectManager.getDefault()
204: .findProject(dir);
205: } catch (IOException ioEx) {
206: // Ignore invalid folders
207: }
208: }
209:
210: if (project != null) {
211: OpenProjectList.getDefault().open(
212: new Project[] { project }, false, true);
213: ProjectTab ptLogial = ProjectTab
214: .findDefault(ProjectTab.ID_LOGICAL);
215: Node root = ptLogial.getExplorerManager()
216: .getRootContext();
217: Node projNode = root.getChildren().findChild(
218: project.getProjectDirectory().getName());
219: try {
220: ptLogial.getExplorerManager().setSelectedNodes(
221: new Node[] { projNode });
222: ptLogial.open();
223: ptLogial.requestActive();
224: } catch (Exception ignore) {
225: // may ignore it
226: }
227: }
228:
229: }
230:
231: }
232:
233: }
234:
235: private class ProjectDirListener extends FileChangeAdapter {
236: public void fileDeleted(FileEvent fe) {
237: recreate = true;
238: }
239: }
240:
241: private class UpdatingMenu extends JMenu implements
242: /*DynamicMenuContent,*/ChangeListener {
243:
244: public UpdatingMenu(Action action) {
245: super (action);
246: }
247:
248: // public JComponent[] synchMenuPresenters(JComponent[] items) {
249: // return getMenuPresenters();
250: // }
251: //
252: // public JComponent[] getMenuPresenters() {
253: // return new JComponent[] { this };
254: // }
255:
256: public void stateChanged(ChangeEvent e) {
257: if (recreate && getModel().isSelected()) {
258: fillSubMenu(this );
259: recreate = false;
260: }
261: }
262:
263: }
264:
265: }
|