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;
043:
044: import java.awt.Color;
045: import java.awt.EventQueue;
046: import java.awt.event.ActionEvent;
047: import java.awt.event.ActionListener;
048: import java.beans.PropertyChangeEvent;
049: import java.beans.PropertyChangeListener;
050: import java.io.File;
051: import java.io.IOException;
052: import java.net.URI;
053: import java.text.Collator;
054: import java.text.MessageFormat;
055: import java.util.ArrayList;
056: import java.util.Collections;
057: import java.util.HashMap;
058: import java.util.Iterator;
059: import java.util.List;
060: import java.util.Map;
061: import java.util.Set;
062: import javax.swing.DefaultListModel;
063: import javax.swing.Icon;
064: import javax.swing.ImageIcon;
065: import javax.swing.JFileChooser;
066: import javax.swing.ListModel;
067: import javax.swing.SwingUtilities;
068: import javax.swing.UIManager;
069: import javax.swing.filechooser.FileFilter;
070: import javax.swing.filechooser.FileSystemView;
071: import javax.swing.filechooser.FileView;
072: import org.netbeans.api.project.Project;
073: import org.netbeans.api.project.ProjectManager;
074: import org.netbeans.api.project.ProjectUtils;
075: import org.netbeans.api.queries.CollocationQuery;
076: import org.netbeans.spi.project.SubprojectProvider;
077: import org.openide.filesystems.FileObject;
078: import org.openide.filesystems.FileUtil;
079: import org.openide.util.Cancellable;
080: import org.openide.util.Exceptions;
081: import org.openide.util.NbBundle;
082: import org.openide.util.RequestProcessor;
083: import org.openide.util.Utilities;
084:
085: /**
086: * Special component on side of project filechooser.
087: */
088: public class ProjectChooserAccessory extends javax.swing.JPanel
089: implements ActionListener, PropertyChangeListener {
090:
091: private RequestProcessor.Task updateSubprojectsTask;
092: private RequestProcessor RP;
093:
094: ModelUpdater modelUpdater; //#101227 -> non-private
095: private Boolean tempSetAsMain;
096:
097: private Map<Project, Set<? extends Project>> subprojectsCache = new HashMap<Project, Set<? extends Project>>(); // #59098
098:
099: /** Creates new form ProjectChooserAccessory */
100: public ProjectChooserAccessory(JFileChooser chooser,
101: boolean isOpenSubprojects, boolean isOpenAsMain) {
102: initComponents();
103:
104: modelUpdater = new ModelUpdater();
105: //#98080
106: RP = new RequestProcessor(ModelUpdater.class.getName(), 1);
107: updateSubprojectsTask = RP.create(modelUpdater);
108: updateSubprojectsTask.setPriority(Thread.MIN_PRIORITY);
109:
110: // Listen on the subproject checkbox to change the option accordingly
111: jCheckBoxSubprojects.setSelected(isOpenSubprojects);
112: jCheckBoxSubprojects.addActionListener(this );
113:
114: // Listen on the main checkbox to change the option accordingly
115: jCheckBoxMain.setSelected(isOpenAsMain);
116: jCheckBoxMain.addActionListener(this );
117:
118: // Listen on the chooser to update the Accessory
119: chooser.addPropertyChangeListener(this );
120:
121: // Set default list model for the subprojects list
122: jListSubprojects.setModel(new DefaultListModel());
123:
124: // Disable the Accessory. JFileChooser does not select a file
125: // by default
126: setAccessoryEnablement(false, 0);
127: }
128:
129: /** This method is called from within the constructor to
130: * initialize the form.
131: * WARNING: Do NOT modify this code. The content of this method is
132: * always regenerated by the Form Editor.
133: */
134: private void initComponents() {//GEN-BEGIN:initComponents
135: java.awt.GridBagConstraints gridBagConstraints;
136:
137: jLabelProjectName = new javax.swing.JLabel();
138: jTextFieldProjectName = new javax.swing.JTextField();
139: jCheckBoxMain = new javax.swing.JCheckBox();
140: jCheckBoxSubprojects = new javax.swing.JCheckBox();
141: jScrollPaneSubprojects = new javax.swing.JScrollPane();
142: jListSubprojects = new javax.swing.JList();
143:
144: setLayout(new java.awt.GridBagLayout());
145:
146: setBorder(new javax.swing.border.EmptyBorder(
147: new java.awt.Insets(0, 12, 0, 0)));
148: jLabelProjectName.setLabelFor(jTextFieldProjectName);
149: org.openide.awt.Mnemonics.setLocalizedText(jLabelProjectName,
150: org.openide.util.NbBundle.getMessage(
151: ProjectChooserAccessory.class,
152: "LBL_PrjChooser_ProjectName_Label"));
153: gridBagConstraints = new java.awt.GridBagConstraints();
154: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
155: gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
156: gridBagConstraints.insets = new java.awt.Insets(0, 0, 2, 0);
157: add(jLabelProjectName, gridBagConstraints);
158: jLabelProjectName.getAccessibleContext().setAccessibleName(
159: org.openide.util.NbBundle
160: .getMessage(ProjectChooserAccessory.class,
161: "AN_ProjectName"));
162: jLabelProjectName.getAccessibleContext()
163: .setAccessibleDescription(
164: org.openide.util.NbBundle.getMessage(
165: ProjectChooserAccessory.class,
166: "AD_ProjectName"));
167:
168: jTextFieldProjectName.setEditable(false);
169: gridBagConstraints = new java.awt.GridBagConstraints();
170: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
171: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
172: gridBagConstraints.weightx = 1.0;
173: gridBagConstraints.insets = new java.awt.Insets(0, 0, 6, 0);
174: add(jTextFieldProjectName, gridBagConstraints);
175:
176: org.openide.awt.Mnemonics.setLocalizedText(jCheckBoxMain,
177: org.openide.util.NbBundle.getMessage(
178: ProjectChooserAccessory.class,
179: "LBL_PrjChooser_Main_CheckBox"));
180: jCheckBoxMain.setMargin(new java.awt.Insets(2, 0, 2, 2));
181: gridBagConstraints = new java.awt.GridBagConstraints();
182: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
183: gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
184: gridBagConstraints.insets = new java.awt.Insets(0, 0, 12, 0);
185: add(jCheckBoxMain, gridBagConstraints);
186: jCheckBoxMain.getAccessibleContext().setAccessibleDescription(
187: org.openide.util.NbBundle.getMessage(
188: ProjectChooserAccessory.class,
189: "ACSD_ProjectChooserAccessory_jCheckBoxMain"));
190:
191: org.openide.awt.Mnemonics.setLocalizedText(
192: jCheckBoxSubprojects, org.openide.util.NbBundle
193: .getMessage(ProjectChooserAccessory.class,
194: "LBL_PrjChooser_Subprojects_CheckBox"));
195: jCheckBoxSubprojects.setMargin(new java.awt.Insets(2, 0, 2, 2));
196: gridBagConstraints = new java.awt.GridBagConstraints();
197: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
198: gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
199: gridBagConstraints.insets = new java.awt.Insets(0, 0, 2, 0);
200: add(jCheckBoxSubprojects, gridBagConstraints);
201: jCheckBoxSubprojects
202: .getAccessibleContext()
203: .setAccessibleDescription(
204: org.openide.util.NbBundle
205: .getMessage(
206: ProjectChooserAccessory.class,
207: "ACSD_ProjectChooserAccessory_jCheckBoxSubprojects"));
208:
209: jListSubprojects
210: .setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
211: jListSubprojects.setEnabled(false);
212: jScrollPaneSubprojects.setViewportView(jListSubprojects);
213: jListSubprojects
214: .getAccessibleContext()
215: .setAccessibleName(
216: org.openide.util.NbBundle
217: .getMessage(
218: ProjectChooserAccessory.class,
219: "ACSN_ProjectChooserAccessory_jListSubprojects"));
220: jListSubprojects
221: .getAccessibleContext()
222: .setAccessibleDescription(
223: org.openide.util.NbBundle
224: .getMessage(
225: ProjectChooserAccessory.class,
226: "ACSD_ProjectChooserAccessory_jListSubprojects"));
227:
228: gridBagConstraints = new java.awt.GridBagConstraints();
229: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
230: gridBagConstraints.gridheight = java.awt.GridBagConstraints.REMAINDER;
231: gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
232: gridBagConstraints.weightx = 1.0;
233: gridBagConstraints.weighty = 1.0;
234: add(jScrollPaneSubprojects, gridBagConstraints);
235:
236: }//GEN-END:initComponents
237:
238: // Variables declaration - do not modify//GEN-BEGIN:variables
239: private javax.swing.JCheckBox jCheckBoxMain;
240: private javax.swing.JCheckBox jCheckBoxSubprojects;
241: private javax.swing.JLabel jLabelProjectName;
242: private javax.swing.JList jListSubprojects;
243: private javax.swing.JScrollPane jScrollPaneSubprojects;
244: private javax.swing.JTextField jTextFieldProjectName;
245:
246: // End of variables declaration//GEN-END:variables
247:
248: // Implementation of action listener ---------------------------------------
249:
250: public void actionPerformed(ActionEvent e) {
251: if (e.getSource() == jCheckBoxSubprojects) {
252: OpenProjectListSettings.getInstance().setOpenSubprojects(
253: jCheckBoxSubprojects.isSelected());
254: } else if (e.getSource() == jCheckBoxMain) {
255: OpenProjectListSettings.getInstance().setOpenAsMain(
256: jCheckBoxMain.isSelected());
257: }
258: }
259:
260: public void propertyChange(PropertyChangeEvent e) {
261: if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(e
262: .getPropertyName())
263: || JFileChooser.SELECTED_FILES_CHANGED_PROPERTY
264: .equals(e.getPropertyName())) {
265:
266: // We have to update the Accessory
267: JFileChooser chooser = (JFileChooser) e.getSource();
268: final ListModel spListModel = jListSubprojects.getModel();
269:
270: final File[] projectDirs;
271: if (chooser.isMultiSelectionEnabled()) {
272: projectDirs = chooser.getSelectedFiles();
273: } else {
274: projectDirs = new File[] { chooser.getSelectedFile() };
275: }
276:
277: // #87119: do not block EQ loading projects
278: jTextFieldProjectName.setText(NbBundle.getMessage(
279: ProjectChooserAccessory.class,
280: "MSG_PrjChooser_WaitMessage"));
281: RequestProcessor.getDefault().post(new Runnable() {
282: public void run() {
283:
284: final List<Project> projects = new ArrayList<Project>(
285: projectDirs.length);
286: for (File dir : projectDirs) {
287: if (dir != null) {
288: Project project = getProject(FileUtil
289: .normalizeFile(dir));
290: if (project != null) {
291: projects.add(project);
292: }
293: }
294: }
295:
296: EventQueue.invokeLater(new Runnable() {
297: public void run() {
298:
299: if (!projects.isEmpty()) {
300: // Enable all components acessory
301: setAccessoryEnablement(true, projects
302: .size());
303:
304: if (projects.size() == 1) {
305: String projectName = ProjectUtils
306: .getInformation(
307: projects.get(0))
308: .getDisplayName();
309: jTextFieldProjectName
310: .setText(projectName);
311: jTextFieldProjectName
312: .setToolTipText(projectName);
313: } else {
314: jTextFieldProjectName
315: .setText(NbBundle
316: .getMessage(
317: ProjectChooserAccessory.class,
318: "LBL_PrjChooser_Multiselection",
319: projects
320: .size()));
321:
322: StringBuffer toolTipText = new StringBuffer(
323: "<html>"); // NOI18N
324: for (Iterator<Project> it = projects
325: .iterator(); it.hasNext();) {
326: Project p = it.next();
327: toolTipText.append(ProjectUtils
328: .getInformation(p)
329: .getDisplayName());
330: if (it.hasNext()) {
331: toolTipText.append("<br>"); // NOI18N
332: }
333: }
334: toolTipText.append("</html>"); // NOI18N
335: jTextFieldProjectName
336: .setToolTipText(toolTipText
337: .toString());
338: }
339:
340: if (spListModel instanceof DefaultListModel) {
341: ((DefaultListModel) spListModel)
342: .clear();
343: } else {
344: jListSubprojects
345: .setListData(new String[0]);
346: }
347:
348: if (modelUpdater != null) { // #72495
349: modelUpdater.projects = projects;
350: updateSubprojectsTask.schedule(100);
351: }
352: } else {
353: // Clear the accessory data if the dir is not project dir
354: jTextFieldProjectName.setText(""); // NOI18N
355: if (modelUpdater != null) { // #72495
356: modelUpdater.projects = null;
357: }
358:
359: if (spListModel instanceof DefaultListModel) {
360: ((DefaultListModel) spListModel)
361: .clear();
362: } else {
363: jListSubprojects
364: .setListData(new String[0]);
365: }
366:
367: // Disable all components in accessory
368: setAccessoryEnablement(false, 0);
369:
370: // But, in case it is a load error, show that:
371: if (projectDirs.length == 1
372: && projectDirs[0] != null) {
373: File dir = FileUtil
374: .normalizeFile(projectDirs[0]);
375: FileObject fo = FileUtil
376: .toFileObject(dir);
377: ProjectManager.getDefault()
378: .clearNonProjectCache(); // #113976: otherwise isProject will be false
379: if (fo != null
380: && fo.isFolder()
381: && ProjectManager
382: .getDefault()
383: .isProject(fo)) {
384: try {
385: Project prj = ProjectManager
386: .getDefault()
387: .findProject(fo);
388: if (prj == null) {
389: jTextFieldProjectName
390: .setText(NbBundle
391: .getMessage(
392: ProjectChooserAccessory.class,
393: "LBL_PrjChooser_Unrecognized"));
394: // Only so it can be focussed and message scrolled accessibly:
395: jLabelProjectName
396: .setEnabled(true);
397: jTextFieldProjectName
398: .setEnabled(true);
399: }
400: } catch (IOException x) {
401: String msg = Exceptions
402: .findLocalizedMessage(x);
403: if (msg == null) {
404: msg = x
405: .getLocalizedMessage();
406: }
407: jTextFieldProjectName
408: .setText(msg);
409: jTextFieldProjectName
410: .setCaretPosition(0);
411: Color error = UIManager
412: .getColor("nb.errorForeground"); // NOI18N
413: if (error != null) {
414: jTextFieldProjectName
415: .setForeground(error);
416: }
417: // Only so it can be focussed and message scrolled accessibly:
418: jLabelProjectName
419: .setEnabled(true);
420: jTextFieldProjectName
421: .setEnabled(true);
422: }
423: }
424: }
425: }
426:
427: }
428: });
429: }
430: });
431: } else if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(e
432: .getPropertyName())) {
433: // Selection lost => disable accessory
434: setAccessoryEnablement(false, 0);
435: }
436: }
437:
438: // Private methods ---------------------------------------------------------
439:
440: private static boolean isProjectDir(File dir) {
441: boolean retVal = false;
442: if (dir != null) {
443: FileObject fo = convertToValidDir(dir);
444: if (fo != null) {
445: if (Utilities.isUnix() && fo.getParent() != null
446: && fo.getParent().getParent() == null) {
447: retVal = false; // Ignore all subfolders of / on unixes (e.g. /net, /proc)
448: } else {
449: retVal = ProjectManager.getDefault().isProject(fo);
450: }
451: }
452: }
453: return retVal;
454: }
455:
456: private static FileObject convertToValidDir(File f) {
457: FileObject fo;
458: File testFile = new File(f.getPath());
459: if (testFile == null || testFile.getParent() == null) {
460: // BTW this means that roots of file systems can't be project
461: // directories.
462: return null;
463: }
464:
465: /**ATTENTION: on Windows may occur dir.isDirectory () == dir.isFile () == true then
466: * its used testFile instead of dir.
467: */
468: if (!testFile.isDirectory()) {
469: return null;
470: }
471:
472: fo = FileUtil.toFileObject(FileUtil.normalizeFile(f));
473: return fo;
474: }
475:
476: private static Project getProject(File dir) {
477: return OpenProjectList.fileToProject(dir);
478: }
479:
480: private void setAccessoryEnablement(boolean enable,
481: int numberOfProjects) {
482: jLabelProjectName.setEnabled(enable);
483: jTextFieldProjectName.setEnabled(enable);
484: jTextFieldProjectName
485: .setForeground(/* i.e. L&F default */null);
486: jCheckBoxSubprojects.setEnabled(enable);
487: jScrollPaneSubprojects.setEnabled(enable);
488:
489: if (numberOfProjects <= 1) {
490: if (tempSetAsMain != null) {
491: jCheckBoxMain.setSelected(tempSetAsMain);
492: tempSetAsMain = null;
493: }
494: jCheckBoxMain.setEnabled(enable);
495: } else if (tempSetAsMain == null) {
496: tempSetAsMain = jCheckBoxMain.isSelected();
497: jCheckBoxMain.setSelected(false);
498: jCheckBoxMain.setEnabled(false);
499: }
500:
501: }
502:
503: /**
504: * Get a slash-separated relative path from f1 to f2, if they are collocated
505: * and this is possible.
506: * May return null.
507: */
508: private static String relativizePath(File f1, File f2) {
509: if (f1 == null || f2 == null) {
510: return null;
511: }
512: if (!CollocationQuery.areCollocated(f1, f2)) {
513: return null;
514: }
515: // Copied from PropertyUtils.relativizeFile, more or less:
516: StringBuffer b = new StringBuffer();
517: File base = f1;
518: String filepath = f2.getAbsolutePath();
519: while (!filepath.startsWith(slashify(base.getAbsolutePath()))) {
520: base = base.getParentFile();
521: if (base == null) {
522: return null;
523: }
524: if (base.equals(f2)) {
525: // #61687: file is a parent of basedir
526: b.append(".."); // NOI18N
527: return b.toString();
528: }
529: b.append("../"); // NOI18N
530: }
531: URI u = base.toURI().relativize(f2.toURI());
532: assert !u.isAbsolute() : u + " from " + f1 + " and " + f2
533: + " with common root " + base;
534: b.append(u.getPath());
535: if (b.charAt(b.length() - 1) == '/') {
536: // file is an existing directory and file.toURI ends in /
537: // we do not want the trailing slash
538: b.setLength(b.length() - 1);
539: }
540: return b.toString();
541: }
542:
543: private static String slashify(String path) {
544: if (path.endsWith(File.separator)) {
545: return path;
546: } else {
547: return path + File.separatorChar;
548: }
549: }
550:
551: // Other methods -----------------------------------------------------------
552:
553: /** Factory method for project chooser
554: */
555: public static JFileChooser createProjectChooser(
556: boolean defaultAccessory) {
557:
558: ProjectManager.getDefault().clearNonProjectCache(); // #41882
559:
560: OpenProjectListSettings opls = OpenProjectListSettings
561: .getInstance();
562: JFileChooser chooser = new ProjectFileChooser();
563: chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
564:
565: if ("GTK"
566: .equals(javax.swing.UIManager.getLookAndFeel().getID())) { // NOI18N
567: // see BugTraq #5027268
568: chooser.putClientProperty(
569: "GTKFileChooser.showDirectoryIcons", Boolean.TRUE); // NOI18N
570: //chooser.putClientProperty("GTKFileChooser.showFileIcons", Boolean.TRUE); // NOI18N
571: }
572:
573: chooser.setApproveButtonText(NbBundle.getMessage(
574: ProjectChooserAccessory.class,
575: "BTN_PrjChooser_ApproveButtonText")); // NOI18N
576: chooser.setApproveButtonMnemonic(NbBundle.getMessage(
577: ProjectChooserAccessory.class,
578: "MNM_PrjChooser_ApproveButtonText").charAt(0)); // NOI18N
579: chooser.setApproveButtonToolTipText(NbBundle.getMessage(
580: ProjectChooserAccessory.class,
581: "BTN_PrjChooser_ApproveButtonTooltipText")); // NOI18N
582: // chooser.setMultiSelectionEnabled( true );
583: chooser.setDialogTitle(NbBundle.getMessage(
584: ProjectChooserAccessory.class, "LBL_PrjChooser_Title")); // NOI18N
585: //#61789 on old macosx (jdk 1.4.1) these two method need to be called in this order.
586: chooser.setAcceptAllFileFilterUsed(false);
587: chooser.setFileFilter(ProjectDirFilter.INSTANCE);
588:
589: // A11Y
590: chooser.getAccessibleContext().setAccessibleName(
591: org.openide.util.NbBundle.getMessage(
592: ProjectChooserAccessory.class,
593: "AN_ProjectChooserAccessory"));
594: chooser.getAccessibleContext().setAccessibleDescription(
595: org.openide.util.NbBundle.getMessage(
596: ProjectChooserAccessory.class,
597: "AD_ProjectChooserAccessory"));
598:
599: if (defaultAccessory) {
600: chooser.setAccessory(new ProjectChooserAccessory(chooser,
601: opls.isOpenSubprojects(), opls.isOpenAsMain()));
602: }
603:
604: File currDir = null;
605: String dir = opls.getLastOpenProjectDir();
606: if (dir != null) {
607: File d = new File(dir);
608: if (d.exists() && d.isDirectory()) {
609: currDir = d;
610: }
611: }
612:
613: FileUtil.preventFileChooserSymlinkTraversal(chooser, currDir);
614: chooser.setFileView(new ProjectFileView(chooser
615: .getFileSystemView()));
616:
617: return chooser;
618:
619: }
620:
621: public void removeNotify() { // #72006
622: super .removeNotify();
623: if (modelUpdater != null) { // #101286 - might be already null
624: modelUpdater.cancel();
625: }
626: modelUpdater = null;
627: subprojectsCache = null;
628: updateSubprojectsTask = null;
629: }
630:
631: // Aditional innerclasses for the file chooser -----------------------------
632:
633: private static class ProjectFileChooser extends JFileChooser {
634:
635: public void approveSelection() {
636: File dir = FileUtil.normalizeFile(getSelectedFile());
637:
638: if (isProjectDir(dir) && getProject(dir) != null) {
639: super .approveSelection();
640: } else {
641: setCurrentDirectory(dir);
642: }
643:
644: }
645:
646: }
647:
648: private static class ProjectDirFilter extends FileFilter {
649:
650: private static final FileFilter INSTANCE = new ProjectDirFilter();
651:
652: public boolean accept(File f) {
653:
654: if (f.isDirectory()) {
655: //#114765
656: if ("CVS".equalsIgnoreCase(f.getName())
657: && new File(f, "Entries").exists()) { //NOI18N
658: return false;
659: }
660: return true; // Directory selected
661: }
662:
663: return false;
664: }
665:
666: public String getDescription() {
667: return NbBundle.getMessage(ProjectDirFilter.class,
668: "LBL_PrjChooser_ProjectDirectoryFilter_Name"); // NOI18N
669: }
670:
671: }
672:
673: private static class ProjectFileView extends FileView {
674:
675: private static final Icon BADGE = new ImageIcon(
676: Utilities
677: .loadImage("org/netbeans/modules/project/ui/resources/projectBadge.gif")); // NOI18N
678: private static final Icon EMPTY = new ImageIcon(
679: Utilities
680: .loadImage("org/netbeans/modules/project/ui/resources/empty.gif")); // NOI18N
681:
682: private FileSystemView fsv;
683: private Icon lastOriginal;
684: private Icon lastMerged;
685:
686: public ProjectFileView(FileSystemView fsv) {
687: this .fsv = fsv;
688: }
689:
690: public Icon getIcon(File _f) {
691: if (!_f.exists()) {
692: // Can happen when a file was deleted on disk while project
693: // dialog was still open. In that case, throws an exception
694: // repeatedly from FSV.gSI during repaint.
695: return null;
696: }
697: File f = FileUtil.normalizeFile(_f);
698: Icon original = fsv.getSystemIcon(f);
699: if (original == null) {
700: // L&F (e.g. GTK) did not specify any icon.
701: original = EMPTY;
702: }
703: if (isProjectDir(f)) {
704: if (original.equals(lastOriginal)) {
705: return lastMerged;
706: }
707: lastOriginal = original;
708: lastMerged = new MergedIcon(original, BADGE, -1, -1);
709: return lastMerged;
710: } else {
711: return original;
712: }
713: }
714:
715: }
716:
717: private static class MergedIcon implements Icon {
718:
719: private Icon icon1;
720: private Icon icon2;
721: private int xMerge;
722: private int yMerge;
723:
724: MergedIcon(Icon icon1, Icon icon2, int xMerge, int yMerge) {
725:
726: this .icon1 = icon1;
727: this .icon2 = icon2;
728:
729: if (xMerge == -1) {
730: xMerge = icon1.getIconWidth() - icon2.getIconWidth();
731: }
732:
733: if (yMerge == -1) {
734: yMerge = icon1.getIconHeight() - icon2.getIconHeight();
735: }
736:
737: this .xMerge = xMerge;
738: this .yMerge = yMerge;
739: }
740:
741: public int getIconHeight() {
742: return Math.max(icon1.getIconHeight(), yMerge
743: + icon2.getIconHeight());
744: }
745:
746: public int getIconWidth() {
747: return Math.max(icon1.getIconWidth(), yMerge
748: + icon2.getIconWidth());
749: }
750:
751: public void paintIcon(java.awt.Component c,
752: java.awt.Graphics g, int x, int y) {
753: icon1.paintIcon(c, g, x, y);
754: icon2.paintIcon(c, g, x + xMerge, y + yMerge);
755: }
756:
757: }
758:
759: class ModelUpdater implements Runnable, Cancellable { //#101227 -> non-private
760: // volatile Project project;
761: volatile List<Project> projects;
762: private DefaultListModel subprojectsToSet;
763: private boolean cancel = false;
764:
765: public void run() {
766:
767: if (!SwingUtilities.isEventDispatchThread()) {
768: if (cancel) {
769: return;
770: }
771: List<Project> currentProjects = projects;
772: if (currentProjects == null) {
773: return;
774: }
775: Map<Project, Set<? extends Project>> cache = subprojectsCache;
776: if (cache == null) {
777: return;
778: }
779:
780: jListSubprojects.setListData(new String[] { NbBundle
781: .getMessage(ProjectChooserAccessory.class,
782: "MSG_PrjChooser_WaitMessage") });
783:
784: List<Project> subprojects = new ArrayList<Project>(
785: currentProjects.size() * 5);
786: for (Project p : currentProjects) {
787: if (cancel)
788: return;
789: addSubprojects(p, subprojects, cache); // Find the projects recursively
790: }
791:
792: if (cancel)
793: return;
794: List<String> subprojectNames = new ArrayList<String>(
795: subprojects.size());
796: if (!subprojects.isEmpty()) {
797: String pattern = NbBundle.getMessage(
798: ProjectChooserAccessory.class,
799: "LBL_PrjChooser_SubprojectName_Format"); // NOI18N
800: File pDir = currentProjects.size() == 1 ? FileUtil
801: .toFile(currentProjects.get(0)
802: .getProjectDirectory()) : null;
803:
804: // Replace projects in the list with formated names
805: for (Project p : subprojects) {
806: if (cancel)
807: return;
808: FileObject spDir = p.getProjectDirectory();
809:
810: // Try to compute relative path
811: String relPath = null;
812: if (pDir != null) { // If only one project is selected
813: relPath = relativizePath(pDir, FileUtil
814: .toFile(spDir));
815: }
816:
817: if (relPath == null) {
818: // Cannot get a relative path; display it as absolute.
819: relPath = FileUtil
820: .getFileDisplayName(spDir);
821: }
822: String displayName = MessageFormat.format(
823: pattern, ProjectUtils.getInformation(p)
824: .getDisplayName(), relPath);
825: subprojectNames.add(displayName);
826: }
827:
828: // Sort the list
829: Collections.sort(subprojectNames, Collator
830: .getInstance());
831: }
832: if (currentProjects != projects || cancel) {
833: return;
834: }
835: DefaultListModel listModel = new DefaultListModel();
836: // Put all the strings into the list model
837: for (String displayName : subprojectNames) {
838: listModel.addElement(displayName);
839: }
840: subprojectsToSet = listModel;
841: if (cancel)
842: return;
843: SwingUtilities.invokeLater(this );
844: return;
845: } else {
846: if (projects == null) {
847: ListModel spListModel = jListSubprojects.getModel();
848: if (spListModel instanceof DefaultListModel) {
849: ((DefaultListModel) spListModel).clear();
850: } else {
851: jListSubprojects.setListData(new String[0]);
852: }
853: jCheckBoxSubprojects.setEnabled(false);
854: } else {
855: jListSubprojects.setModel(subprojectsToSet);
856: // If no soubprojects checkbox should be disabled
857: jCheckBoxSubprojects.setEnabled(!subprojectsToSet
858: .isEmpty());
859: projects = null;
860: }
861: }
862:
863: }
864:
865: /** Gets all subprojects recursively
866: */
867: void addSubprojects(Project p, List<Project> result,
868: Map<Project, Set<? extends Project>> cache) {
869: if (cancel)
870: return;
871: Set<? extends Project> subprojects = cache.get(p);
872: if (subprojects == null) {
873: SubprojectProvider spp = p.getLookup().lookup(
874: SubprojectProvider.class);
875: if (spp != null) {
876: if (cancel)
877: return;
878: subprojects = spp.getSubprojects();
879: } else {
880: subprojects = Collections.emptySet();
881: }
882: cache.put(p, subprojects);
883: }
884: for (Project sp : subprojects) {
885: if (cancel)
886: return;
887: if (!result.contains(sp)) {
888: result.add(sp);
889:
890: //#70029: only add sp's subprojects if sp is not already in result,
891: //to prevent StackOverflow caused by misconfigured projects:
892: addSubprojects(sp, result, cache);
893: }
894: }
895:
896: }
897:
898: public boolean cancel() {
899: cancel = true;
900: // we don't really care that much to wait for cancelation here..
901: return true;
902: }
903:
904: }
905:
906: }
|