001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2008 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-2008 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.ruby.rubyproject.ui.wizards;
043:
044: import java.awt.Component;
045: import java.io.File;
046: import java.util.HashSet;
047: import java.util.Set;
048: import javax.swing.DefaultListModel;
049: import javax.swing.DefaultListCellRenderer;
050: import javax.swing.JFileChooser;
051: import javax.swing.JList;
052: import javax.swing.event.ListSelectionListener;
053: import javax.swing.event.ListSelectionEvent;
054: import org.netbeans.modules.ruby.rubyproject.RubyProject;
055: import org.netbeans.api.project.SourceGroup;
056: import org.netbeans.api.project.Sources;
057: import org.netbeans.modules.ruby.rubyproject.ui.customizer.RubySourceRootsUi;
058: import org.openide.filesystems.FileObject;
059: import org.openide.filesystems.FileUtil;
060: import org.netbeans.api.project.FileOwnerQuery;
061: import org.netbeans.api.project.Project;
062: import org.openide.awt.Mnemonics;
063:
064: /**
065: * List of source/test roots
066: * @author tzezula
067: */
068: public final class FolderList extends javax.swing.JPanel {
069:
070: public static final String PROP_FILES = "files"; //NOI18N
071: public static final String PROP_LAST_USED_DIR = "lastUsedDir"; //NOI18N
072:
073: private final String fcMessage;
074: private File projectFolder;
075: private File lastUsedFolder;
076: private FolderList relatedFolderList;
077:
078: /** Creates new form FolderList */
079: public FolderList(String label, String accessibleDesc,
080: String fcMessage, String addButtonAccessibleDesc,
081: String addButtonLabel, String removeButtonAccessibleDesc,
082: String removeButtonLabel) {
083: this .fcMessage = fcMessage;
084: initComponents();
085: Mnemonics.setLocalizedText(this .jLabel1, label);
086: Mnemonics.setLocalizedText(this .addButton, addButtonLabel);
087: Mnemonics
088: .setLocalizedText(this .removeButton, removeButtonLabel);
089: this .jLabel1.setText(label);
090: this .roots.getAccessibleContext().setAccessibleDescription(
091: accessibleDesc);
092: this .roots.setCellRenderer(new Renderer());
093: this .roots.setModel(new DefaultListModel());
094: this .roots
095: .addListSelectionListener(new ListSelectionListener() {
096: public void valueChanged(ListSelectionEvent e) {
097: if (!e.getValueIsAdjusting()) {
098: removeButton.setEnabled(roots
099: .getSelectedIndices().length != 0);
100: }
101: }
102: });
103: this .addButton.getAccessibleContext().setAccessibleDescription(
104: addButtonAccessibleDesc);
105: this .removeButton.getAccessibleContext()
106: .setAccessibleDescription(removeButtonAccessibleDesc);
107: this .removeButton.setEnabled(false);
108: }
109:
110: public void setProjectFolder(File projectFolder) {
111: this .projectFolder = projectFolder;
112: }
113:
114: public void setRelatedFolderList(FolderList relatedFolderList) {
115: this .relatedFolderList = relatedFolderList;
116: }
117:
118: public File[] getFiles() {
119: Object[] files = ((DefaultListModel) this .roots.getModel())
120: .toArray();
121: File[] result = new File[files.length];
122: System.arraycopy(files, 0, result, 0, files.length);
123: return result;
124: }
125:
126: public void setFiles(File[] files) {
127: DefaultListModel model = ((DefaultListModel) this .roots
128: .getModel());
129: model.clear();
130: for (int i = 0; i < files.length; i++) {
131: model.addElement(files[i]);
132: }
133: if (files.length > 0) {
134: this .roots.setSelectedIndex(0);
135: }
136: }
137:
138: public void setLastUsedDir(File lastUsedDir) {
139: if (this .lastUsedFolder == null ? lastUsedDir != null
140: : !this .lastUsedFolder.equals(lastUsedDir)) {
141: File oldValue = this .lastUsedFolder;
142: this .lastUsedFolder = lastUsedDir;
143: this .firePropertyChange(PROP_LAST_USED_DIR, oldValue,
144: this .lastUsedFolder);
145: }
146: }
147:
148: public File getLastUsedDir() {
149: return this .lastUsedFolder;
150: }
151:
152: /** This method is called from within the constructor to
153: * initialize the form.
154: * WARNING: Do NOT modify this code. The content of this method is
155: * always regenerated by the Form Editor.
156: */
157: private void initComponents() {//GEN-BEGIN:initComponents
158: java.awt.GridBagConstraints gridBagConstraints;
159:
160: jLabel1 = new javax.swing.JLabel();
161: jScrollPane1 = new javax.swing.JScrollPane();
162: roots = new javax.swing.JList();
163: addButton = new javax.swing.JButton();
164: removeButton = new javax.swing.JButton();
165:
166: setLayout(new java.awt.GridBagLayout());
167:
168: jLabel1.setLabelFor(roots);
169: jLabel1.setText("jLabel1");
170: gridBagConstraints = new java.awt.GridBagConstraints();
171: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
172: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
173: gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
174: gridBagConstraints.weightx = 1.0;
175: gridBagConstraints.insets = new java.awt.Insets(0, 0, 6, 0);
176: add(jLabel1, gridBagConstraints);
177:
178: jScrollPane1.setViewportView(roots);
179:
180: gridBagConstraints = new java.awt.GridBagConstraints();
181: gridBagConstraints.gridheight = java.awt.GridBagConstraints.REMAINDER;
182: gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
183: gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
184: gridBagConstraints.weightx = 1.0;
185: gridBagConstraints.weighty = 1.0;
186: gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 12);
187: add(jScrollPane1, gridBagConstraints);
188:
189: addButton
190: .setText(java.util.ResourceBundle
191: .getBundle(
192: "org/netbeans/modules/ruby/rubyproject/ui/wizards/Bundle")
193: .getString("CTL_AddFolder"));
194: addButton
195: .addActionListener(new java.awt.event.ActionListener() {
196: public void actionPerformed(
197: java.awt.event.ActionEvent evt) {
198: addButtonActionPerformed(evt);
199: }
200: });
201:
202: gridBagConstraints = new java.awt.GridBagConstraints();
203: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
204: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
205: gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
206: add(addButton, gridBagConstraints);
207:
208: removeButton
209: .setText(java.util.ResourceBundle
210: .getBundle(
211: "org/netbeans/modules/ruby/rubyproject/ui/wizards/Bundle")
212: .getString("CTL_RemoveFolder"));
213: removeButton
214: .addActionListener(new java.awt.event.ActionListener() {
215: public void actionPerformed(
216: java.awt.event.ActionEvent evt) {
217: removeButtonActionPerformed(evt);
218: }
219: });
220:
221: gridBagConstraints = new java.awt.GridBagConstraints();
222: gridBagConstraints.gridx = 1;
223: gridBagConstraints.gridy = 2;
224: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
225: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
226: gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
227: gridBagConstraints.insets = new java.awt.Insets(12, 0, 0, 0);
228: add(removeButton, gridBagConstraints);
229:
230: }//GEN-END:initComponents
231:
232: private void removeButtonActionPerformed(
233: java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeButtonActionPerformed
234: Object[] selection = this .roots.getSelectedValues();
235: for (int i = 0; i < selection.length; i++) {
236: ((DefaultListModel) this .roots.getModel())
237: .removeElement(selection[i]);
238: }
239: this .firePropertyChange(PROP_FILES, null, null);
240: }//GEN-LAST:event_removeButtonActionPerformed
241:
242: private void addButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addButtonActionPerformed
243: JFileChooser chooser = new JFileChooser();
244: FileUtil.preventFileChooserSymlinkTraversal(chooser, null);
245: chooser.setDialogTitle(this .fcMessage);
246: chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
247: chooser.setMultiSelectionEnabled(true);
248: if (this .lastUsedFolder != null
249: && this .lastUsedFolder.isDirectory()) {
250: chooser.setCurrentDirectory(this .lastUsedFolder);
251: } else if (this .projectFolder != null
252: && this .projectFolder.isDirectory()) {
253: chooser.setCurrentDirectory(this .projectFolder);
254: }
255: if (chooser.showOpenDialog(this ) == JFileChooser.APPROVE_OPTION) {
256: File[] files = chooser.getSelectedFiles();
257: int[] indecesToSelect = new int[files.length];
258: DefaultListModel model = (DefaultListModel) this .roots
259: .getModel();
260: Set<File> invalidRoots = new HashSet<File>();
261: File[] relatedFolders = this .relatedFolderList == null ? new File[0]
262: : this .relatedFolderList.getFiles();
263: for (int i = 0, index = model.size(); i < files.length; i++, index++) {
264: File normalizedFile = FileUtil.normalizeFile(files[i]);
265: if (!isValidRoot(normalizedFile, relatedFolders,
266: this .projectFolder)) {
267: invalidRoots.add(normalizedFile);
268: } else {
269: int pos = model.indexOf(normalizedFile);
270: if (pos == -1) {
271: model.addElement(normalizedFile);
272: indecesToSelect[i] = index;
273: } else {
274: indecesToSelect[i] = pos;
275: }
276: }
277: }
278: this .roots.setSelectedIndices(indecesToSelect);
279: this .firePropertyChange(PROP_FILES, null, null);
280: File cd = chooser.getCurrentDirectory();
281: if (cd != null) {
282: this .setLastUsedDir(FileUtil.normalizeFile(cd));
283: }
284: if (!invalidRoots.isEmpty()) {
285: RubySourceRootsUi.showIllegalRootsDialog(invalidRoots);
286: }
287: }
288: }//GEN-LAST:event_addButtonActionPerformed
289:
290: static boolean isValidRoot(File file, File[] relatedRoots,
291: File projectFolder) {
292: Project p;
293: if ((p = FileOwnerQuery.getOwner(file.toURI())) != null
294: && !file.getAbsolutePath().startsWith(
295: projectFolder.getAbsolutePath()
296: + File.separatorChar)) {
297: final Sources sources = p.getLookup().lookup(Sources.class);
298: if (sources == null) {
299: return false;
300: }
301: final SourceGroup[] sourceGroups = sources
302: .getSourceGroups(Sources.TYPE_GENERIC);
303: final SourceGroup[] javaGroups = sources
304: .getSourceGroups(RubyProject.SOURCES_TYPE_RUBY);
305: final SourceGroup[] groups = new SourceGroup[sourceGroups.length
306: + javaGroups.length];
307: System.arraycopy(sourceGroups, 0, groups, 0,
308: sourceGroups.length);
309: System.arraycopy(javaGroups, 0, groups,
310: sourceGroups.length, javaGroups.length);
311: final FileObject projectDirectory = p.getProjectDirectory();
312: final FileObject fileObject = FileUtil.toFileObject(file);
313: if (projectDirectory == null || fileObject == null) {
314: return false;
315: }
316: for (int i = 0; i < groups.length; i++) {
317: final FileObject sgRoot = groups[i].getRootFolder();
318: if (fileObject.equals(sgRoot)) {
319: return false;
320: }
321: if (!projectDirectory.equals(sgRoot)
322: && FileUtil.isParentOf(sgRoot, fileObject)) {
323: return false;
324: }
325: }
326: return true;
327: } else if (contains(file, relatedRoots)) {
328: return false;
329: }
330: return true;
331: }
332:
333: private static boolean contains(File folder, File[] roots) {
334: String path = folder.getAbsolutePath();
335: for (int i = 0; i < roots.length; i++) {
336: String rootPath = roots[i].getAbsolutePath();
337: if (rootPath.equals(path)
338: || path.startsWith(rootPath + File.separatorChar)) {
339: return true;
340: }
341: }
342: return false;
343: }
344:
345: private static class Renderer extends DefaultListCellRenderer {
346: public @Override
347: Component getListCellRendererComponent(JList list,
348: Object value, int index, boolean isSelected,
349: boolean cellHasFocus) {
350: File f = (File) value;
351: String message = f.getAbsolutePath();
352: return super .getListCellRendererComponent(list, message,
353: index, isSelected, cellHasFocus);
354: }
355:
356: }
357:
358: // Variables declaration - do not modify//GEN-BEGIN:variables
359: private javax.swing.JButton addButton;
360: private javax.swing.JLabel jLabel1;
361: private javax.swing.JScrollPane jScrollPane1;
362: private javax.swing.JButton removeButton;
363: private javax.swing.JList roots;
364: // End of variables declaration//GEN-END:variables
365:
366: }
|