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.beaninfo.editors;
043:
044: import java.awt.event.ActionEvent;
045: import java.awt.event.KeyEvent;
046: import java.awt.Component;
047: import java.awt.Container;
048: import java.awt.Dialog;
049: import java.awt.KeyboardFocusManager;
050: import java.beans.PropertyChangeEvent;
051: import java.beans.PropertyChangeListener;
052: import java.beans.PropertyEditorSupport;
053: import java.io.File;
054: import java.io.FilenameFilter;
055: import java.util.logging.Level;
056: import java.util.logging.Logger;
057: import javax.swing.AbstractAction;
058: import javax.swing.AbstractButton;
059: import javax.swing.Action;
060: import javax.swing.JComboBox;
061: import javax.swing.JComponent;
062: import javax.swing.JFileChooser;
063: import javax.swing.JScrollBar;
064: import javax.swing.KeyStroke;
065: import org.openide.nodes.Node;
066: import org.openide.explorer.propertysheet.ExPropertyEditor;
067: import org.openide.explorer.propertysheet.PropertyEnv;
068: import org.openide.util.HelpCtx;
069: import org.openide.util.NbBundle;
070:
071: /**
072: * PropertyEditor for <code>java.io.File</code>.
073: *
074: * @author Jaroslav Tulach, David Strupl, Peter Zavadsky, Jesse Glick
075: */
076: public class FileEditor extends PropertyEditorSupport implements
077: ExPropertyEditor, PropertyChangeListener {
078:
079: /** Name of the property obtained from the feature descriptor.*/
080: static final String PROPERTY_SHOW_DIRECTORIES = "directories"; //NOI18N
081:
082: /** Name of the property obtained from the feature descriptor.*/
083: static final String PROPERTY_SHOW_FILES = "files"; //NOI18N
084:
085: /** Name of the property obtained from the feature descriptor.*/
086: static final String PROPERTY_FILTER = "filter"; //NOI18N
087:
088: /** Name of the property obtained from the feature descriptor.*/
089: static final String PROPERTY_CURRENT_DIR = "currentDir"; //NOI18N
090:
091: /** Name of the property obtained from the feature descriptor. */
092: static final String PROPERTY_BASE_DIR = "baseDir"; // NOI18N
093:
094: /** Name of the property obtained from the feature descriptor. */
095: static final String PROPERTY_FILE_HIDING = "file_hiding"; // NOI18N
096:
097: /** Openning mode.*/
098: private int mode = JFileChooser.FILES_AND_DIRECTORIES;
099:
100: /** Flag indicating whether to choose directories. Default value is <code>true</code>. */
101: private boolean directories = true;
102: /** Flag indicating whether to choose files. Default value is <code>true</code>. */
103: private boolean files = true;
104: /** Flag indicating whether to hide files marked as hidden. Default value is <code>false</code>. */
105: private boolean fileHiding = false;
106: /** Filter for files to show. */
107: private javax.swing.filechooser.FileFilter fileFilter;
108: /** Current firectory. */
109: private File currentDirectory;
110: /** Base directory to which to show relative path, if is set. */
111: private File baseDirectory;
112:
113: /** Caches last used directory. */
114: static File lastCurrentDir;
115:
116: private PropertyEnv env;
117:
118: /** Cached chooser.
119: * If you don't cache it, MountIterator in core flickers and behaves weirdly,
120: * because apparently PropertyPanel will call getCustomEditor repeatedly and
121: * refresh the display each time.
122: * XXX MountIterator is dead so is this still necessary? -jglick
123: */
124: private JFileChooser chooser;
125:
126: /** whether the value can be edited -- default to true */
127: private boolean editable = true;
128:
129: /**
130: * This method is called by the IDE to pass
131: * the environment to the property editor.
132: * @param env Environment passed by the ide.
133: */
134: public void attachEnv(PropertyEnv env) {
135: this .env = env;
136:
137: // clearing to defaults
138: directories = true;
139: files = true;
140: fileFilter = null;
141: fileHiding = false;
142:
143: Object dirs = env.getFeatureDescriptor().getValue(
144: PROPERTY_SHOW_DIRECTORIES);
145: if (dirs instanceof Boolean) {
146: directories = ((Boolean) dirs).booleanValue();
147: } // XXX else if != null, warn
148: Object fil = env.getFeatureDescriptor().getValue(
149: PROPERTY_SHOW_FILES);
150: if (fil instanceof Boolean) {
151: files = ((Boolean) fil).booleanValue();
152: } // XXX else if != null, warn
153: Object filter = env.getFeatureDescriptor().getValue(
154: PROPERTY_FILTER);
155: if (filter instanceof FilenameFilter) {
156: fileFilter = new DelegatingFilenameFilter(
157: (FilenameFilter) filter);
158: } else if (filter instanceof javax.swing.filechooser.FileFilter) {
159: fileFilter = (javax.swing.filechooser.FileFilter) filter;
160: } else if (filter instanceof java.io.FileFilter) {
161: fileFilter = new DelegatingFileFilter(
162: (java.io.FileFilter) filter);
163: } // XXX else if != null, warn
164:
165: Object curDir = env.getFeatureDescriptor().getValue(
166: PROPERTY_CURRENT_DIR);
167: if (curDir instanceof File) {
168: currentDirectory = (File) curDir;
169: if (!currentDirectory.isDirectory()) {
170: Logger.getAnonymousLogger().warning(
171: "java.io.File will not accept currentDir="
172: + currentDirectory); // NOI18N
173: currentDirectory = null;
174: }
175: } // XXX else if != null, warn
176:
177: Object baseDir = env.getFeatureDescriptor().getValue(
178: PROPERTY_BASE_DIR);
179: if (baseDir instanceof File) {
180: baseDirectory = (File) baseDir;
181: // As baseDir accept only directories in their absolute form.
182: if (!baseDirectory.isDirectory()
183: || !baseDirectory.isAbsolute()) {
184: Logger.getAnonymousLogger().warning(
185: "java.io.File will not accept baseDir="
186: + baseDirectory); // NOI18N
187: baseDirectory = null;
188: }
189: } // XXX else if != null, warn
190: if (files) {
191: mode = directories ? JFileChooser.FILES_AND_DIRECTORIES
192: : JFileChooser.FILES_ONLY;
193: } else {
194: mode = directories ? JFileChooser.DIRECTORIES_ONLY
195: : JFileChooser.FILES_AND_DIRECTORIES; // both false, what now? XXX warn
196: }
197:
198: Object fileHide = env.getFeatureDescriptor().getValue(
199: PROPERTY_FILE_HIDING);
200: if (fileHide instanceof Boolean) {
201: fileHiding = ((Boolean) fileHide).booleanValue();
202: }
203:
204: if (env.getFeatureDescriptor() instanceof Node.Property) {
205: Node.Property prop = (Node.Property) env
206: .getFeatureDescriptor();
207: editable = prop.canWrite();
208: }
209: }
210:
211: /** Returns human readable form of the edited value.
212: * @return string reprezentation
213: */
214: public String getAsText() {
215: File file = (File) getValue();
216: if (file == null) {
217: return ""; // NOI18N
218: }
219: String path = file.getPath();
220: // Dot is more friendly to people though Java itself would prefer blank:
221: if ("".equals(path))
222: path = "."; // NOI18N
223: return path;
224: }
225:
226: /** Parses the given string and should create a new instance of the
227: * edited object.
228: * @param str string reprezentation of the file (used as a parameter for File).
229: * @throws IllegalArgumentException If the given string cannot be parsed
230: */
231: public void setAsText(String str) throws IllegalArgumentException {
232: if (str == null) {
233: throw new IllegalArgumentException("null"); // NOI18N
234: }
235: if ("".equals(str)) { // NOI18N
236: setValue(null);
237: return;
238: }
239: // See getAsText.
240: if (".".equals(str))
241: str = ""; // NOI18N
242: setValue(new File(str));
243: }
244:
245: /** Custon editor.
246: * @return Returns custom editor component.
247: */
248: public Component getCustomEditor() {
249: if (!editable) {
250: String info = "";
251: Object curVal = getValue();
252: if (curVal instanceof java.io.File) {
253: info = ((java.io.File) curVal).getAbsolutePath();
254: }
255: return new StringCustomEditor(info, false, true, null,
256: this , env);
257: }
258: if (chooser == null) {
259: chooser = createHackedFileChooser();
260:
261: File originalFile = (File) getValue();
262: if (originalFile != null && !originalFile.isAbsolute()
263: && baseDirectory != null) {
264: originalFile = new File(baseDirectory, originalFile
265: .getPath());
266: }
267: if (currentDirectory != null) {
268: chooser.setCurrentDirectory(currentDirectory);
269: } else if (originalFile != null
270: && originalFile.getParentFile() != null) {
271: chooser.setCurrentDirectory(originalFile
272: .getParentFile());
273: chooser.setSelectedFile(originalFile);
274: } else if (lastCurrentDir != null) {
275: chooser.setCurrentDirectory(lastCurrentDir);
276: }
277: chooser.setFileSelectionMode(mode);
278: if (fileFilter != null) {
279: chooser.setFileFilter(fileFilter);
280: }
281: switch (mode) {
282: case JFileChooser.FILES_AND_DIRECTORIES:
283: chooser
284: .setDialogTitle(getString("CTL_DialogTitleFilesAndDirs"));
285: break;
286: case JFileChooser.FILES_ONLY:
287: chooser
288: .setDialogTitle(getString("CTL_DialogTitleFiles"));
289: break;
290: case JFileChooser.DIRECTORIES_ONLY:
291: chooser
292: .setDialogTitle(getString("CTL_DialogTitleDirs"));
293: break;
294: }
295: chooser.setFileHidingEnabled(fileHiding);
296:
297: chooser.setControlButtonsAreShown(false);
298:
299: chooser.addPropertyChangeListener(
300: JFileChooser.SELECTED_FILE_CHANGED_PROPERTY, this );
301:
302: HelpCtx.setHelpIDString(chooser, getHelpCtx().getHelpID());
303: }
304:
305: return chooser;
306: }
307:
308: /** Implements PropertyEditor method.
309: * @return Returns true.
310: */
311: public boolean supportsCustomEditor() {
312: return true;
313: }
314:
315: /** Should create a string insertable to the newly generated source code.
316: * @return initialization string
317: */
318: public String getJavaInitializationString() {
319: File value = (File) getValue();
320: if (value == null) {
321: return "null"; // NOI18N
322: } else {
323: // [PENDING] not a full escape of filenames, but enough to at least
324: // handle normal Windows backslashes
325: if (baseDirectory != null && !value.isAbsolute()) {
326: return "new java.io.File(" // NOI18N
327: + stringify(baseDirectory.getPath()) + ", " // NOI18N
328: + stringify(value.getPath()) + ")"; // NOI18N
329: } else {
330: return "new java.io.File(" // NOI18N
331: + stringify(value.getAbsolutePath()) + ")"; // NOI18N
332: }
333: }
334: }
335:
336: static String stringify(String in) {
337: StringBuffer buf = new StringBuffer(in.length() * 2 + 2);
338: buf.append('"'); // NOI18N
339: for (int i = 0; i < in.length(); i++) {
340: char c = in.charAt(i);
341: if (c == '\\' || c == '"') { // NOI18N
342: buf.append('\\'); // NOI18N
343: }
344: buf.append(c);
345: }
346: buf.append('"'); // NOI18N
347: return buf.toString();
348: }
349:
350: /** Gets help context. */
351: private HelpCtx getHelpCtx() {
352: return new HelpCtx(FileEditor.class);
353: }
354:
355: /** Gets localized string. Helper method. */
356: private static String getString(String key) {
357: return NbBundle.getBundle(FileEditor.class).getString(key);
358: }
359:
360: /** Gets relative path of file to specified directory only for case the file
361: * is in directory tree.
362: * @param baseDir base directory
363: * @param file file which relative path to <code>baseDir</code> is needed
364: * @return relative path or <code>null</code> can't be resolved
365: * or if the <code>file</code> is not under <code>baseDir</code> tree */
366: static String getChildRelativePath(File baseDir, File file) {
367: // Handle hypothetical weird situations where file is in baseDir
368: // but the prefixes do not match. E.g.:
369: // file=\foo\bar.txt (assumed to be on C:) baseDir=c:\foo
370: if (file.equals(baseDir)) {
371: // The empty pathname, not ".", is correct here I think...
372: // Try making new File(new File("/tmp", x)) for x in {".", ""}
373: return ""; // NOI18N
374: }
375: StringBuffer buf = new StringBuffer(file.getPath().length());
376: buf.append(file.getName());
377: for (File parent = file.getParentFile(); parent != null; parent = parent
378: .getParentFile()) {
379: if (parent.equals(baseDir)) {
380: return buf.toString();
381: }
382: buf.insert(0, File.separatorChar);
383: buf.insert(0, parent.getName());
384: }
385: return null;
386: }
387:
388: /** Property change listaner attached to the JFileChooser chooser. */
389: public void propertyChange(PropertyChangeEvent e) {
390: JFileChooser chooser = (JFileChooser) e.getSource();
391: File f = chooser.getSelectedFile();
392: if (f == null) {
393: return;
394: }
395: if (!files && f.isFile())
396: return;
397: if (!directories && f.isDirectory())
398: return;
399:
400: if (baseDirectory != null) {
401: String rel = getChildRelativePath(baseDirectory, f);
402: if (rel != null) {
403: f = new File(rel);
404: }
405: }
406:
407: // use to be setValue(f) - the next line is
408: // workaround for JDK bug 4533419
409: // it should be returned back to setValue(f) after the
410: // mentioned bug is fixed in JDK.
411: setValue(new File(f.getPath()));
412:
413: lastCurrentDir = chooser.getCurrentDirectory();
414: }
415:
416: // XXX #18270. Enter doesn't work when expecting folder change,
417: // Accessibility problem. We hack default behaviour here.
418: /** Creates hacked fileChooser, responding on Enter the way it
419: * performs folder change. */
420: public static JFileChooser createHackedFileChooser() {
421: JFileChooser chooser = new JFileChooser();
422: hackFileChooser(chooser);
423: return chooser;
424: }
425:
426: /** Hacks fileChooser, responding on Enter the way it
427: * performs folder change. */
428: public static void hackFileChooser(final JFileChooser chooser) {
429: chooser.getAccessibleContext().setAccessibleDescription(
430: getString("ACSD_FileEditor"));
431:
432: //issue 31605 - make escape work properly
433: //Get the existing action key on ESCAPE
434: // XXX is this hack still necessary?
435: final Object key = chooser.getInputMap(
436: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).get(
437: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
438:
439: Action close = new AbstractAction() {
440: public void actionPerformed(ActionEvent ae) {
441: Component comp = KeyboardFocusManager
442: .getCurrentKeyboardFocusManager()
443: .getFocusOwner();
444: if (key != null) {
445: //if there was an action, do it first
446: Action a = chooser.getActionMap().get(key);
447: if (a != null) {
448: a.actionPerformed(ae);
449: }
450: }
451: if (comp.getParent() == null) {
452: //then we were editing a file name, and the editor
453: //was removed - we don't want to close the dialog
454: return;
455: }
456:
457: Container c = chooser.getTopLevelAncestor();
458: //The action *may* have already hidden the panel (works
459: //intermittently)
460: if (c instanceof Dialog) {
461: if (((Dialog) c).isVisible()) {
462: ((Dialog) c).setVisible(false);
463: ((Dialog) c).dispose();
464: }
465: }
466: }
467: };
468: chooser.getInputMap(
469: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
470: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close");
471: chooser.getActionMap().put("close", close);
472: }
473:
474: private static class ButtonHider implements PropertyChangeListener {
475: public void propertyChange(PropertyChangeEvent pce) {
476: if (JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY
477: .equals(pce.getPropertyName())) {
478: JFileChooser jfc = (JFileChooser) pce.getSource();
479: try {
480: hideShowButtons(jfc, Boolean.TRUE.equals(pce
481: .getNewValue()));
482: } catch (Exception e) {
483: Logger.getLogger(FileEditor.class.getName()).log(
484: Level.WARNING, null, e);
485: }
486: }
487: }
488:
489: private void hideShowButtons(Container cont, boolean val) {
490: if (cont instanceof JComboBox || cont instanceof JScrollBar) {
491: return;
492: }
493: Component[] c = cont.getComponents();
494: for (int i = 0; i < c.length; i++) {
495: if (c[i] instanceof Container) {
496: hideShowButtons((Container) c[i], val);
497: }
498: if (c[i] instanceof AbstractButton) {
499: c[i].setVisible(val);
500: }
501: }
502: }
503: }
504:
505: /** Wraps java.io.FileFilter to javax.swing.filechooser.FileFilter. */
506: static class DelegatingFileFilter extends
507: javax.swing.filechooser.FileFilter {
508: private java.io.FileFilter filter;
509:
510: public DelegatingFileFilter(java.io.FileFilter f) {
511: this .filter = f;
512: }
513:
514: public boolean accept(File f) {
515: return filter.accept(f);
516: }
517:
518: public String getDescription() {
519: // [PENDING] what should we return?
520: return null;
521: }
522:
523: } // End of class DelegatingFileFilter.
524:
525: /** Wraps FilenameFilter to javax.swing.filechooser.FileFilter. */
526: static class DelegatingFilenameFilter extends
527: javax.swing.filechooser.FileFilter {
528: private FilenameFilter filter;
529:
530: public DelegatingFilenameFilter(FilenameFilter f) {
531: this .filter = f;
532: }
533:
534: /** Calls the filenameFilter's accept method with arguments
535: * created from the original object f.
536: */
537: public boolean accept(File f) {
538: return filter.accept(f.getParentFile(), f.getName());
539: }
540:
541: public String getDescription() {
542: // [PENDING] what should we return?
543: return null;
544: }
545: } // End of class DelegatingFilenameFilter.
546:
547: }
|