Line 0
Link Here
|
|
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. |
5 |
* |
6 |
* Oracle and Java are registered trademarks of Oracle and/or its affiliates. |
7 |
* Other names may be trademarks of their respective owners. |
8 |
* |
9 |
* The contents of this file are subject to the terms of either the GNU |
10 |
* General Public License Version 2 only ("GPL") or the Common |
11 |
* Development and Distribution License("CDDL") (collectively, the |
12 |
* "License"). You may not use this file except in compliance with the |
13 |
* License. You can obtain a copy of the License at |
14 |
* http://www.netbeans.org/cddl-gplv2.html |
15 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
16 |
* specific language governing permissions and limitations under the |
17 |
* License. When distributing the software, include this License Header |
18 |
* Notice in each file and include the License file at |
19 |
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this |
20 |
* particular file as subject to the "Classpath" exception as provided |
21 |
* by Oracle in the GPL Version 2 section of the License file that |
22 |
* accompanied this code. If applicable, add the following below the |
23 |
* License Header, with the fields enclosed by brackets [] replaced by |
24 |
* your own identifying information: |
25 |
* "Portions Copyrighted [year] [name of copyright owner]" |
26 |
* |
27 |
* Contributor(s): |
28 |
* |
29 |
* The Original Software is NetBeans. The Initial Developer of the Original |
30 |
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun |
31 |
* Microsystems, Inc. All Rights Reserved. |
32 |
* |
33 |
* If you wish your version of this file to be governed by only the CDDL |
34 |
* or only the GPL Version 2, indicate your decision by adding |
35 |
* "[Contributor] elects to include this software in this distribution |
36 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
37 |
* single choice of license, a recipient has the option to distribute |
38 |
* your version of this file under either the CDDL, the GPL Version 2 or |
39 |
* to extend the choice of license to its licensees as provided above. |
40 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
41 |
* Version 2 license, then the option applies only if the new code is |
42 |
* made subject to such option by the copyright holder. |
43 |
*/ |
44 |
package org.openide.filesystems; |
45 |
|
46 |
import java.awt.Component; |
47 |
import java.awt.FileDialog; |
48 |
import java.awt.Frame; |
49 |
import java.awt.HeadlessException; |
50 |
import java.awt.KeyboardFocusManager; |
51 |
import java.io.File; |
52 |
import java.util.ArrayList; |
53 |
import java.util.List; |
54 |
import javax.swing.Icon; |
55 |
import javax.swing.JFileChooser; |
56 |
import javax.swing.SwingUtilities; |
57 |
import javax.swing.filechooser.FileFilter; |
58 |
import javax.swing.filechooser.FileSystemView; |
59 |
import javax.swing.filechooser.FileView; |
60 |
import org.netbeans.modules.openide.filesystems.FileFilterSupport; |
61 |
import org.openide.filesystems.FileUtil; |
62 |
import org.openide.util.*; |
63 |
|
64 |
/** |
65 |
* Utility class for working with JFileChoosers. In particular, remembering |
66 |
* the last-used directory for a given file is made transparent. You pass an |
67 |
* ad-hoc string key to the constructor (the fully qualified name of the |
68 |
* calling class is good for uniqueness, and there is a constructor that takes |
69 |
* a <code>Class</code> object as an argument for this purpose). That key is |
70 |
* used to look up the most recently-used directory from any previous invocations |
71 |
* with the same key. This makes it easy to have your user interface |
72 |
* “remember” where the user keeps particular types of files, and |
73 |
* saves the user from having to navigate through the same set of directories |
74 |
* every time they need to locate a file from a particular place. |
75 |
* <p/> |
76 |
* <code>FileChooserBuilder</code>'s methods each return <code>this</code>, so |
77 |
* it is possible to chain invocations to simplify setting up a file chooser. |
78 |
* Example usage: |
79 |
* <pre> |
80 |
* <font color="gray">//The default dir to use if no value is stored</font> |
81 |
* File home = new File (System.getProperty("user.home") + File.separator + "lib"); |
82 |
* <font color="gray">//Now build a file chooser and invoke the dialog in one line of code</font> |
83 |
* <font color="gray">//"libraries-dir" is our unique key</font> |
84 |
* File toAdd = new FileChooserBuilder ("libraries-dir").setTitle("Add Library"). |
85 |
* setDefaultWorkingDirectory(home).setApproveText("Add").showOpenDialog(); |
86 |
* <font color="gray">//Result will be null if the user clicked cancel or closed the dialog w/o OK</font> |
87 |
* if (toAdd != null) { |
88 |
* //do something |
89 |
* } |
90 |
*</pre> |
91 |
* <p/> |
92 |
* Instances of this class are intended to be thrown away after use. Typically |
93 |
* you create a builder, set it to create file choosers as you wish, then |
94 |
* use it to show a dialog or create a file chooser you then do something |
95 |
* with. |
96 |
* <p/> |
97 |
* Supports the most common subset of JFileChooser functionality; if you |
98 |
* need to do something exotic with a file chooser, you are probably better |
99 |
* off creating your own. |
100 |
* <p/> |
101 |
* <b>Note:</b> If you use the constructor that takes a <code>Class</code> object, |
102 |
* please use <code>new FileChooserBuilder(MyClass.class)</code>, not |
103 |
* <code>new FileChooserBuilder(getClass())</code>. This avoids unexpected |
104 |
* behavior in the case of subclassing. |
105 |
* |
106 |
* @author Tim Boudreau |
107 |
*/ |
108 |
public class FileChooserBuilder { |
109 |
private boolean dirsOnly; |
110 |
private BadgeProvider badger; |
111 |
private String title; |
112 |
private String approveText; |
113 |
//Just in case... |
114 |
private static boolean PREVENT_SYMLINK_TRAVERSAL = |
115 |
!Boolean.getBoolean("allow.filechooser.symlink.traversal"); //NOI18N |
116 |
private final String dirKey; |
117 |
private File failoverDir; |
118 |
private FileFilter filter; |
119 |
private boolean fileHiding; |
120 |
private boolean controlButtonsShown = true; |
121 |
private String aDescription; |
122 |
private boolean filesOnly; |
123 |
private static final boolean DONT_STORE_DIRECTORIES = |
124 |
Boolean.getBoolean("forget.recent.dirs"); |
125 |
private SelectionApprover approver; |
126 |
private final List<FileFilter> filters = new ArrayList<FileFilter>(3); |
127 |
private boolean useAcceptAllFileFilter = true; |
128 |
/** |
129 |
* Create a new FileChooserBuilder using the name of the passed class |
130 |
* as the metadata for looking up a starting directory from previous |
131 |
* application sessions or invocations. |
132 |
* @param type A non-null class object, typically the calling class |
133 |
*/ |
134 |
public FileChooserBuilder(Class type) { |
135 |
this(type.getName()); |
136 |
} |
137 |
|
138 |
/** |
139 |
* Create a new FileChooserBuilder. The passed key is used as a key |
140 |
* into NbPreferences to look up the directory the file chooser should |
141 |
* initially be rooted on. |
142 |
* |
143 |
* @param dirKey A non-null ad-hoc string. If a FileChooser was previously |
144 |
* used with the same string as is passed, then the initial directory |
145 |
*/ |
146 |
public FileChooserBuilder(String dirKey) { |
147 |
Parameters.notNull("dirKey", dirKey); |
148 |
this.dirKey = dirKey; |
149 |
} |
150 |
|
151 |
/** |
152 |
* Set whether or not any file choosers created by this builder will show |
153 |
* only directories. |
154 |
* @param val true if files should not be shown |
155 |
* @return this |
156 |
*/ |
157 |
public FileChooserBuilder setDirectoriesOnly(boolean val) { |
158 |
dirsOnly = val; |
159 |
assert !filesOnly : "FilesOnly and DirsOnly are mutually exclusive"; |
160 |
return this; |
161 |
} |
162 |
|
163 |
public FileChooserBuilder setFilesOnly(boolean val) { |
164 |
filesOnly = val; |
165 |
assert !dirsOnly : "FilesOnly and DirsOnly are mutually exclusive"; |
166 |
return this; |
167 |
} |
168 |
|
169 |
/** |
170 |
* Provide an implementation of BadgeProvider which will "badge" the |
171 |
* icons of some files. |
172 |
* |
173 |
* @param provider A badge provider which will alter the icon of files |
174 |
* or folders that may be of particular interest to the user |
175 |
* @return this |
176 |
*/ |
177 |
public FileChooserBuilder setBadgeProvider(BadgeProvider provider) { |
178 |
this.badger = provider; |
179 |
return this; |
180 |
} |
181 |
|
182 |
/** |
183 |
* Set the dialog title for any JFileChoosers created by this builder. |
184 |
* @param val A localized, human-readable title |
185 |
* @return this |
186 |
*/ |
187 |
public FileChooserBuilder setTitle(String val) { |
188 |
title = val; |
189 |
return this; |
190 |
} |
191 |
|
192 |
/** |
193 |
* Set the text on the OK button for any file chooser dialogs produced |
194 |
* by this builder. |
195 |
* @param val A short, localized, human-readable string |
196 |
* @return this |
197 |
*/ |
198 |
public FileChooserBuilder setApproveText(String val) { |
199 |
approveText = val; |
200 |
return this; |
201 |
} |
202 |
|
203 |
/** |
204 |
* Set a file filter which filters the list of selectable files. |
205 |
* @param filter |
206 |
* @return this |
207 |
*/ |
208 |
public FileChooserBuilder setFileFilter (FileFilter filter) { |
209 |
this.filter = filter; |
210 |
return this; |
211 |
} |
212 |
|
213 |
/** |
214 |
* Determines whether the <code>AcceptAll FileFilter</code> is used |
215 |
* as an available choice in the choosable filter list. |
216 |
* If false, the <code>AcceptAll</code> file filter is removed from |
217 |
* the list of available file filters. |
218 |
* If true, the <code>AcceptAll</code> file filter will become the |
219 |
* the actively used file filter. |
220 |
* @param accept whether the <code>AcceptAll FileFilter</code> is used |
221 |
* @return this |
222 |
* @since 8.3 |
223 |
*/ |
224 |
public FileChooserBuilder setAcceptAllFileFilterUsed(boolean accept) { |
225 |
useAcceptAllFileFilter = accept; |
226 |
return this; |
227 |
} |
228 |
|
229 |
/** |
230 |
* Set the current directory which should be used <b>only if</b> |
231 |
* a last-used directory cannot be found for the key string passed |
232 |
* into this builder's constructor. |
233 |
* @param dir A directory to root any created file choosers on if |
234 |
* there is no stored path for this builder's key |
235 |
* @return this |
236 |
*/ |
237 |
public FileChooserBuilder setDefaultWorkingDirectory (File dir) { |
238 |
failoverDir = dir; |
239 |
return this; |
240 |
} |
241 |
|
242 |
/** |
243 |
* Enable file hiding in any created file choosers |
244 |
* @param fileHiding Whether or not to hide files. Default is no. |
245 |
* @return this |
246 |
*/ |
247 |
public FileChooserBuilder setFileHiding(boolean fileHiding) { |
248 |
this.fileHiding = fileHiding; |
249 |
return this; |
250 |
} |
251 |
|
252 |
/** |
253 |
* Show/hide control buttons |
254 |
* @param val Whether or not to hide files. Default is no. |
255 |
* @return this |
256 |
*/ |
257 |
public FileChooserBuilder setControlButtonsAreShown(boolean val) { |
258 |
this.controlButtonsShown = val; |
259 |
return this; |
260 |
} |
261 |
|
262 |
/** |
263 |
* Set the accessible description for any file choosers created by this |
264 |
* builder |
265 |
* @param aDescription The description |
266 |
* @return this |
267 |
*/ |
268 |
public FileChooserBuilder setAccessibleDescription(String aDescription) { |
269 |
this.aDescription = aDescription; |
270 |
return this; |
271 |
} |
272 |
|
273 |
/** |
274 |
* Create a JFileChooser that conforms to the parameters set in this |
275 |
* builder. |
276 |
* @return A file chooser |
277 |
*/ |
278 |
public JFileChooser createFileChooser() { |
279 |
JFileChooser result = new SavedDirFileChooser(dirKey, failoverDir, |
280 |
force, approver); |
281 |
prepareFileChooser(result); |
282 |
return result; |
283 |
} |
284 |
|
285 |
private boolean force = false; |
286 |
/** |
287 |
* Force use of the failover directory - i.e. ignore the directory key |
288 |
* passed in. |
289 |
* @param val |
290 |
* @return this |
291 |
*/ |
292 |
public FileChooserBuilder forceUseOfDefaultWorkingDirectory(boolean val) { |
293 |
this.force = val; |
294 |
return this; |
295 |
} |
296 |
|
297 |
/** |
298 |
* Tries to find an appropriate component to parent the file chooser to |
299 |
* when showing a dialog. |
300 |
* @return this |
301 |
*/ |
302 |
private Component findDialogParent() { |
303 |
Component parent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); |
304 |
if (parent == null) { |
305 |
parent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); |
306 |
} |
307 |
if (parent == null) { |
308 |
Frame[] f = Frame.getFrames(); |
309 |
parent = f.length == 0 ? null : f[f.length - 1]; |
310 |
} |
311 |
return parent; |
312 |
} |
313 |
|
314 |
/** |
315 |
* Show an open dialog that allows multiple selection. |
316 |
* @return An array of files, or null if the user cancelled the dialog |
317 |
*/ |
318 |
public File[] showMultiOpenDialog() { |
319 |
JFileChooser chooser = createFileChooser(); |
320 |
chooser.setMultiSelectionEnabled(true); |
321 |
int result = chooser.showOpenDialog(findDialogParent()); |
322 |
if (JFileChooser.APPROVE_OPTION == result) { |
323 |
File[] files = chooser.getSelectedFiles(); |
324 |
return files == null ? new File[0] : files; |
325 |
} else { |
326 |
return null; |
327 |
} |
328 |
} |
329 |
|
330 |
/** |
331 |
* Show an open dialog with a file chooser set up according to the |
332 |
* parameters of this builder. |
333 |
* @return A file if the user clicks the accept button and a file or |
334 |
* folder was selected at the time the user clicked cancel. |
335 |
*/ |
336 |
public File showOpenDialog() { |
337 |
JFileChooser chooser = createFileChooser(); |
338 |
if( Boolean.getBoolean("nb.native.filechooser") ) { //NOI18N |
339 |
FileDialog fileDialog = createFileDialog( chooser.getCurrentDirectory() ); |
340 |
if( null != fileDialog ) { |
341 |
return showFileDialog(fileDialog, FileDialog.LOAD ); |
342 |
} |
343 |
} |
344 |
chooser.setMultiSelectionEnabled(false); |
345 |
int dlgResult = chooser.showOpenDialog(findDialogParent()); |
346 |
if (JFileChooser.APPROVE_OPTION == dlgResult) { |
347 |
File result = chooser.getSelectedFile(); |
348 |
if (result != null && !result.exists()) { |
349 |
result = null; |
350 |
} |
351 |
return result; |
352 |
} else { |
353 |
return null; |
354 |
} |
355 |
|
356 |
} |
357 |
|
358 |
/** |
359 |
* Show a save dialog with the file chooser set up according to the |
360 |
* parameters of this builder. |
361 |
* @return A file if the user clicks the accept button and a file or |
362 |
* folder was selected at the time the user clicked cancel. |
363 |
*/ |
364 |
public File showSaveDialog() { |
365 |
JFileChooser chooser = createFileChooser(); |
366 |
if( Boolean.getBoolean("nb.native.filechooser") ) { //NOI18N |
367 |
FileDialog fileDialog = createFileDialog( chooser.getCurrentDirectory() ); |
368 |
if( null != fileDialog ) { |
369 |
return showFileDialog( fileDialog, FileDialog.SAVE ); |
370 |
} |
371 |
} |
372 |
int result = chooser.showSaveDialog(findDialogParent()); |
373 |
if (JFileChooser.APPROVE_OPTION == result) { |
374 |
return chooser.getSelectedFile(); |
375 |
} else { |
376 |
return null; |
377 |
} |
378 |
} |
379 |
|
380 |
private File showFileDialog( FileDialog fileDialog, int mode ) { |
381 |
String oldFileDialogProp = System.getProperty("apple.awt.fileDialogForDirectories"); //NOI18N |
382 |
if( dirsOnly ) { |
383 |
System.setProperty("apple.awt.fileDialogForDirectories", "true"); //NOI18N |
384 |
} |
385 |
fileDialog.setMode( mode ); |
386 |
fileDialog.setVisible(true); |
387 |
if( dirsOnly ) { |
388 |
if( null != oldFileDialogProp ) { |
389 |
System.setProperty("apple.awt.fileDialogForDirectories", oldFileDialogProp); //NOI18N |
390 |
} else { |
391 |
System.clearProperty("apple.awt.fileDialogForDirectories"); //NOI18N |
392 |
} |
393 |
} |
394 |
if( fileDialog.getDirectory() != null && fileDialog.getFile() != null ) { |
395 |
String selFile = fileDialog.getFile(); |
396 |
File dir = new File( fileDialog.getDirectory() ); |
397 |
return new File( dir, selFile ); |
398 |
} |
399 |
return null; |
400 |
} |
401 |
|
402 |
private void prepareFileChooser(JFileChooser chooser) { |
403 |
chooser.setFileSelectionMode(dirsOnly ? JFileChooser.DIRECTORIES_ONLY |
404 |
: filesOnly ? JFileChooser.FILES_ONLY : |
405 |
JFileChooser.FILES_AND_DIRECTORIES); |
406 |
chooser.setFileHidingEnabled(fileHiding); |
407 |
chooser.setControlButtonsAreShown(controlButtonsShown); |
408 |
chooser.setAcceptAllFileFilterUsed(useAcceptAllFileFilter); |
409 |
if (title != null) { |
410 |
chooser.setDialogTitle(title); |
411 |
} |
412 |
if (approveText != null) { |
413 |
chooser.setApproveButtonText(approveText); |
414 |
} |
415 |
if (badger != null) { |
416 |
chooser.setFileView(new CustomFileView(new BadgeIconProvider(badger), |
417 |
chooser.getFileSystemView())); |
418 |
} |
419 |
if (PREVENT_SYMLINK_TRAVERSAL) { |
420 |
FileUtil.preventFileChooserSymlinkTraversal(chooser, |
421 |
chooser.getCurrentDirectory()); |
422 |
} |
423 |
if (filter != null) { |
424 |
chooser.setFileFilter(filter); |
425 |
} |
426 |
if (aDescription != null) { |
427 |
chooser.getAccessibleContext().setAccessibleDescription(aDescription); |
428 |
} |
429 |
if (!filters.isEmpty()) { |
430 |
for (FileFilter f : filters) { |
431 |
chooser.addChoosableFileFilter(f); |
432 |
} |
433 |
} |
434 |
} |
435 |
|
436 |
private FileDialog createFileDialog( File currentDirectory ) { |
437 |
if( badger != null ) |
438 |
return null; |
439 |
if( !Boolean.getBoolean("nb.native.filechooser") ) |
440 |
return null; |
441 |
if( dirsOnly && !BaseUtilities.isMac() ) |
442 |
return null; |
443 |
Component parentComponent = findDialogParent(); |
444 |
Frame parentFrame = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, parentComponent); |
445 |
FileDialog fileDialog = new FileDialog(parentFrame); |
446 |
if (title != null) { |
447 |
fileDialog.setTitle(title); |
448 |
} |
449 |
if( null != currentDirectory ) |
450 |
fileDialog.setDirectory(currentDirectory.getAbsolutePath()); |
451 |
return fileDialog; |
452 |
} |
453 |
|
454 |
/** |
455 |
* Equivalent to calling <code>JFileChooser.addChoosableFileFilter(filter)</code>. |
456 |
* Adds another file filter that can be displayed in the file filters combo |
457 |
* box in the file chooser. |
458 |
* |
459 |
* @param filter The file filter to add |
460 |
* @return this |
461 |
* @since 7.26.0 |
462 |
*/ |
463 |
public FileChooserBuilder addFileFilter (FileFilter filter) { |
464 |
filters.add (filter); |
465 |
return this; |
466 |
} |
467 |
|
468 |
/** |
469 |
* Add all default file filters to the file chooser. |
470 |
* |
471 |
* @see MIMEResolver.Registration#showInFileChooser() |
472 |
* @see MIMEResolver.ExtensionRegistration#showInFileChooser() |
473 |
* @return this |
474 |
* @since 8.1 |
475 |
*/ |
476 |
public FileChooserBuilder addDefaultFileFilters() { |
477 |
filters.addAll(FileFilterSupport.findRegisteredFileFilters()); |
478 |
return this; |
479 |
} |
480 |
|
481 |
/** |
482 |
* Set a selection approver which can display an "Overwrite file?" |
483 |
* or similar dialog if necessary, when the user presses the accept button |
484 |
* in the file chooser dialog. |
485 |
* |
486 |
* @param approver A SelectionApprover which will determine if the selection |
487 |
* is valid |
488 |
* @return this |
489 |
* @since 7.26.0 |
490 |
*/ |
491 |
public FileChooserBuilder setSelectionApprover (SelectionApprover approver) { |
492 |
this.approver = approver; |
493 |
return this; |
494 |
} |
495 |
|
496 |
/** |
497 |
* Object which can approve the selection (enabling the OK button or |
498 |
* equivalent) in a JFileChooser. Equivalent to overriding |
499 |
* <code>JFileChooser.approveSelection()</code> |
500 |
* @since 7.26.0 |
501 |
*/ |
502 |
public interface SelectionApprover { |
503 |
/** |
504 |
* Approve the selection, enabling the dialog to be closed. Called by |
505 |
* the JFileChooser's <code>approveSelection()</code> method. Use this |
506 |
* interface if you want to, for example, show a dialog asking |
507 |
* "Overwrite File X?" or similar. |
508 |
* |
509 |
* @param selection The selected file(s) at the time the user presses |
510 |
* the Open, Save or OK button |
511 |
* @return true if the selection is accepted, false if it is not and |
512 |
* the dialog should not be closed |
513 |
*/ |
514 |
public boolean approve (File[] selection); |
515 |
} |
516 |
|
517 |
private static final class SavedDirFileChooser extends JFileChooser { |
518 |
private final String dirKey; |
519 |
private final SelectionApprover approver; |
520 |
SavedDirFileChooser(String dirKey, File failoverDir, boolean force, SelectionApprover approver) { |
521 |
this.dirKey = dirKey; |
522 |
this.approver = approver; |
523 |
if (force && failoverDir != null && failoverDir.exists() && failoverDir.isDirectory()) { |
524 |
setCurrentDirectory(failoverDir); |
525 |
} else { |
526 |
String path = DONT_STORE_DIRECTORIES ? null : |
527 |
NbPreferences.forModule(FileChooserBuilder.class).get(dirKey, null); |
528 |
if (path != null) { |
529 |
File f = new File(path); |
530 |
if (f.exists() && f.isDirectory()) { |
531 |
setCurrentDirectory(f); |
532 |
} else if (failoverDir != null) { |
533 |
setCurrentDirectory(failoverDir); |
534 |
} |
535 |
} else if (failoverDir != null) { |
536 |
setCurrentDirectory(failoverDir); |
537 |
} |
538 |
} |
539 |
} |
540 |
|
541 |
@Override |
542 |
public void approveSelection() { |
543 |
if (approver != null) { |
544 |
File[] selected = getSelectedFiles(); |
545 |
final File sf = getSelectedFile(); |
546 |
if ((selected == null || selected.length == 0) && sf != null) { |
547 |
selected = new File[] { sf }; |
548 |
} |
549 |
boolean approved = approver.approve(selected); |
550 |
if (approved) { |
551 |
super.approveSelection(); |
552 |
} |
553 |
} else { |
554 |
super.approveSelection(); |
555 |
} |
556 |
} |
557 |
|
558 |
@Override |
559 |
public int showDialog(Component parent, String approveButtonText) throws HeadlessException { |
560 |
int result = super.showDialog(parent, approveButtonText); |
561 |
if (result == APPROVE_OPTION) { |
562 |
saveCurrentDir(); |
563 |
} |
564 |
return result; |
565 |
} |
566 |
|
567 |
private void saveCurrentDir() { |
568 |
File dir = super.getCurrentDirectory(); |
569 |
if (!DONT_STORE_DIRECTORIES && dir != null && dir.exists() && dir.isDirectory()) { |
570 |
NbPreferences.forModule(FileChooserBuilder.class).put(dirKey, dir.getPath()); |
571 |
} |
572 |
} |
573 |
} |
574 |
|
575 |
//Can open this API later if there is a use-case |
576 |
interface IconProvider { |
577 |
public Icon getIcon(File file, Icon orig); |
578 |
} |
579 |
|
580 |
/** |
581 |
* Provides "badges" for icons that indicate files or folders of particular |
582 |
* interest to the user. |
583 |
* @see FileChooserBuilder#setBadgeProvider |
584 |
*/ |
585 |
public interface BadgeProvider { |
586 |
/** |
587 |
* Get the badge the passed file should use. <b>Note:</b> this method |
588 |
* is called for every visible file. The negative test (deciding |
589 |
* <i>not</i> to badge a file) should be very, very fast and immediately |
590 |
* return null. |
591 |
* @param file The file in question |
592 |
* @return an icon or null if no change to the appearance of the file |
593 |
* is needed |
594 |
*/ |
595 |
public Icon getBadge(File file); |
596 |
|
597 |
/** |
598 |
* Get the x offset for badges produced by this provider. This is |
599 |
* the location of the badge icon relative to the real icon for the |
600 |
* file. |
601 |
* @return a rightward pixel offset |
602 |
*/ |
603 |
public int getXOffset(); |
604 |
|
605 |
/** |
606 |
* Get the y offset for badges produced by this provider. This is |
607 |
* the location of the badge icon relative to the real icon for the |
608 |
* file. |
609 |
* @return a downward pixel offset |
610 |
*/ |
611 |
public int getYOffset(); |
612 |
} |
613 |
|
614 |
private static final class BadgeIconProvider implements IconProvider { |
615 |
|
616 |
private final BadgeProvider badger; |
617 |
|
618 |
public BadgeIconProvider(BadgeProvider badger) { |
619 |
this.badger = badger; |
620 |
} |
621 |
|
622 |
public Icon getIcon(File file, Icon orig) { |
623 |
Icon badge = badger.getBadge(file); |
624 |
if (badge != null && orig != null) { |
625 |
return new MergedIcon(orig, badge, badger.getXOffset(), |
626 |
badger.getYOffset()); |
627 |
} |
628 |
return orig; |
629 |
} |
630 |
} |
631 |
|
632 |
private static final class CustomFileView extends FileView { |
633 |
|
634 |
private final IconProvider provider; |
635 |
private final FileSystemView view; |
636 |
|
637 |
CustomFileView(IconProvider provider, FileSystemView view) { |
638 |
this.provider = provider; |
639 |
this.view = view; |
640 |
} |
641 |
|
642 |
@Override |
643 |
public Icon getIcon(File f) { |
644 |
Icon result = view.getSystemIcon(f); |
645 |
result = provider.getIcon(f, result); |
646 |
return result; |
647 |
} |
648 |
} |
649 |
|
650 |
private static class MergedIcon implements Icon { |
651 |
|
652 |
private Icon icon1; |
653 |
private Icon icon2; |
654 |
private int xMerge; |
655 |
private int yMerge; |
656 |
|
657 |
MergedIcon(Icon icon1, Icon icon2, int xMerge, int yMerge) { |
658 |
assert icon1 != null; |
659 |
assert icon2 != null; |
660 |
this.icon1 = icon1; |
661 |
this.icon2 = icon2; |
662 |
|
663 |
if (xMerge == -1) { |
664 |
xMerge = icon1.getIconWidth() - icon2.getIconWidth(); |
665 |
} |
666 |
|
667 |
if (yMerge == -1) { |
668 |
yMerge = icon1.getIconHeight() - icon2.getIconHeight(); |
669 |
} |
670 |
|
671 |
this.xMerge = xMerge; |
672 |
this.yMerge = yMerge; |
673 |
} |
674 |
|
675 |
public int getIconHeight() { |
676 |
return Math.max(icon1.getIconHeight(), yMerge + icon2.getIconHeight()); |
677 |
} |
678 |
|
679 |
public int getIconWidth() { |
680 |
return Math.max(icon1.getIconWidth(), yMerge + icon2.getIconWidth()); |
681 |
} |
682 |
|
683 |
public void paintIcon(java.awt.Component c, java.awt.Graphics g, int x, int y) { |
684 |
icon1.paintIcon(c, g, x, y); |
685 |
icon2.paintIcon(c, g, x + xMerge, y + yMerge); |
686 |
} |
687 |
} |
688 |
} |