001: /*
002: * Copyright 2002-2005 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025: package sun.swing.plaf.synth;
026:
027: import javax.swing.plaf.synth.*;
028: import java.awt.*;
029: import java.awt.event.*;
030: import java.beans.*;
031: import java.io.File;
032: import java.io.IOException;
033: import java.util.*;
034: import java.util.regex.*;
035:
036: import javax.swing.*;
037: import javax.swing.border.*;
038: import javax.swing.event.*;
039: import javax.swing.filechooser.*;
040: import javax.swing.plaf.*;
041: import javax.swing.plaf.basic.BasicDirectoryModel;
042: import javax.swing.plaf.basic.BasicFileChooserUI;
043:
044: import sun.swing.*;
045:
046: /**
047: * Synth FileChooserUI.
048: *
049: * Note: This class is abstract. It does not actually create the file chooser GUI.
050: * <p>
051: * Note that the classes in the com.sun.java.swing.plaf.synth
052: * package are not
053: * part of the core Java APIs. They are a part of Sun's JDK and JRE
054: * distributions. Although other licensees may choose to distribute
055: * these classes, developers cannot depend on their availability in
056: * non-Sun implementations. Additionally this API may change in
057: * incompatible ways between releases. While this class is public, it
058: * shoud be considered an implementation detail, and subject to change.
059: *
060: * @version 1.24, 05/05/07
061: * @author Leif Samuelsson
062: * @author Jeff Dinkins
063: */
064: public abstract class SynthFileChooserUI extends BasicFileChooserUI
065: implements SynthUI {
066: private JButton approveButton, cancelButton;
067:
068: private SynthStyle style;
069:
070: // Some generic FileChooser functions
071: private Action fileNameCompletionAction = new FileNameCompletionAction();
072:
073: private FileFilter actualFileFilter = null;
074: private GlobFilter globFilter = null;
075:
076: private boolean readOnly;
077:
078: public static ComponentUI createUI(JComponent c) {
079: return new SynthFileChooserUIImpl((JFileChooser) c);
080: }
081:
082: public SynthFileChooserUI(JFileChooser b) {
083: super (b);
084: }
085:
086: public SynthContext getContext(JComponent c) {
087: return new SynthContext(c, Region.FILE_CHOOSER, style,
088: getComponentState(c));
089: }
090:
091: protected SynthContext getContext(JComponent c, int state) {
092: Region region = SynthLookAndFeel.getRegion(c);
093: return new SynthContext(c, Region.FILE_CHOOSER, style, state);
094: }
095:
096: private Region getRegion(JComponent c) {
097: return SynthLookAndFeel.getRegion(c);
098: }
099:
100: private int getComponentState(JComponent c) {
101: if (c.isEnabled()) {
102: if (c.isFocusOwner()) {
103: return ENABLED | FOCUSED;
104: }
105: return ENABLED;
106: }
107: return DISABLED;
108: }
109:
110: private void updateStyle(JComponent c) {
111: SynthStyle newStyle = SynthLookAndFeel.getStyleFactory()
112: .getStyle(c, Region.FILE_CHOOSER);
113: if (newStyle != style) {
114: if (style != null) {
115: style.uninstallDefaults(getContext(c, ENABLED));
116: }
117: style = newStyle;
118: SynthContext context = getContext(c, ENABLED);
119: style.installDefaults(context);
120: Border border = c.getBorder();
121: if (border == null || border instanceof UIResource) {
122: c
123: .setBorder(new UIBorder(style.getInsets(
124: context, null)));
125: }
126:
127: directoryIcon = style.getIcon(context,
128: "FileView.directoryIcon");
129: fileIcon = style.getIcon(context, "FileView.fileIcon");
130: computerIcon = style.getIcon(context,
131: "FileView.computerIcon");
132: hardDriveIcon = style.getIcon(context,
133: "FileView.hardDriveIcon");
134: floppyDriveIcon = style.getIcon(context,
135: "FileView.floppyDriveIcon");
136:
137: newFolderIcon = style.getIcon(context,
138: "FileChooser.newFolderIcon");
139: upFolderIcon = style.getIcon(context,
140: "FileChooser.upFolderIcon");
141: homeFolderIcon = style.getIcon(context,
142: "FileChooser.homeFolderIcon");
143: detailsViewIcon = style.getIcon(context,
144: "FileChooser.detailsViewIcon");
145: listViewIcon = style.getIcon(context,
146: "FileChooser.listViewIcon");
147: }
148: }
149:
150: public void installUI(JComponent c) {
151: super .installUI(c);
152: SwingUtilities.replaceUIActionMap(c, createActionMap());
153: }
154:
155: public void installComponents(JFileChooser fc) {
156: SynthContext context = getContext(fc, ENABLED);
157:
158: cancelButton = new JButton(cancelButtonText);
159: cancelButton.setName("SynthFileChooser.cancelButton");
160: cancelButton.setIcon(context.getStyle().getIcon(context,
161: "FileChooser.cancelIcon"));
162: cancelButton.setMnemonic(cancelButtonMnemonic);
163: cancelButton.setToolTipText(cancelButtonToolTipText);
164: cancelButton.addActionListener(getCancelSelectionAction());
165:
166: approveButton = new JButton(getApproveButtonText(fc));
167: approveButton.setName("SynthFileChooser.approveButton");
168: approveButton.setIcon(context.getStyle().getIcon(context,
169: "FileChooser.okIcon"));
170: approveButton.setMnemonic(getApproveButtonMnemonic(fc));
171: approveButton.setToolTipText(getApproveButtonToolTipText(fc));
172: approveButton.addActionListener(getApproveSelectionAction());
173:
174: }
175:
176: public void uninstallComponents(JFileChooser fc) {
177: fc.removeAll();
178: }
179:
180: protected void installListeners(JFileChooser fc) {
181: super .installListeners(fc);
182:
183: getModel().addListDataListener(new ListDataListener() {
184: public void contentsChanged(ListDataEvent e) {
185: // Update the selection after JList has been updated
186: new DelayedSelectionUpdater();
187: }
188:
189: public void intervalAdded(ListDataEvent e) {
190: new DelayedSelectionUpdater();
191: }
192:
193: public void intervalRemoved(ListDataEvent e) {
194: }
195: });
196:
197: }
198:
199: private class DelayedSelectionUpdater implements Runnable {
200: DelayedSelectionUpdater() {
201: SwingUtilities.invokeLater(this );
202: }
203:
204: public void run() {
205: updateFileNameCompletion();
206: }
207: }
208:
209: ActionMap createActionMap() {
210: ActionMap map = new ActionMapUIResource();
211: map.put("approveSelection", getApproveSelectionAction());
212: map.put("cancelSelection", getCancelSelectionAction());
213: map.put("Go Up", getChangeToParentDirectoryAction());
214: map.put("fileNameCompletion", getFileNameCompletionAction());
215: return map;
216: }
217:
218: protected void installDefaults(JFileChooser fc) {
219: super .installDefaults(fc);
220: updateStyle(fc);
221: readOnly = UIManager.getBoolean("FileChooser.readOnly");
222: }
223:
224: protected void uninstallDefaults(JFileChooser fc) {
225: super .uninstallDefaults(fc);
226:
227: SynthContext context = getContext(getFileChooser(), ENABLED);
228: style.uninstallDefaults(context);
229: style = null;
230: }
231:
232: protected void installIcons(JFileChooser fc) {
233: // The icons are installed in updateStyle, not here
234: }
235:
236: public void update(Graphics g, JComponent c) {
237: SynthContext context = getContext(c);
238:
239: if (c.isOpaque()) {
240: g.setColor(style.getColor(context, ColorType.BACKGROUND));
241: g.fillRect(0, 0, c.getWidth(), c.getHeight());
242: }
243:
244: style.getPainter(context).paintFileChooserBackground(context,
245: g, 0, 0, c.getWidth(), c.getHeight());
246: paint(context, g);
247: }
248:
249: public void paintBorder(SynthContext context, Graphics g, int x,
250: int y, int w, int h) {
251: }
252:
253: public void paint(Graphics g, JComponent c) {
254: SynthContext context = getContext(c);
255:
256: paint(context, g);
257: }
258:
259: protected void paint(SynthContext context, Graphics g) {
260: }
261:
262: abstract public void setFileName(String fileName);
263:
264: abstract public String getFileName();
265:
266: protected void doSelectedFileChanged(PropertyChangeEvent e) {
267: }
268:
269: protected void doSelectedFilesChanged(PropertyChangeEvent e) {
270: }
271:
272: protected void doDirectoryChanged(PropertyChangeEvent e) {
273: File currentDirectory = getFileChooser().getCurrentDirectory();
274: if (!readOnly && currentDirectory != null) {
275: getNewFolderAction().setEnabled(
276: FilePane.canWrite(currentDirectory));
277: }
278: }
279:
280: protected void doAccessoryChanged(PropertyChangeEvent e) {
281: }
282:
283: protected void doFileSelectionModeChanged(PropertyChangeEvent e) {
284: }
285:
286: protected void doMultiSelectionChanged(PropertyChangeEvent e) {
287: if (!getFileChooser().isMultiSelectionEnabled()) {
288: getFileChooser().setSelectedFiles(null);
289: }
290: }
291:
292: protected void doControlButtonsChanged(PropertyChangeEvent e) {
293: if (getFileChooser().getControlButtonsAreShown()) {
294: approveButton
295: .setText(getApproveButtonText(getFileChooser()));
296: approveButton
297: .setToolTipText(getApproveButtonToolTipText(getFileChooser()));
298: }
299: }
300:
301: protected void doAncestorChanged(PropertyChangeEvent e) {
302: }
303:
304: public PropertyChangeListener createPropertyChangeListener(
305: JFileChooser fc) {
306: return new SynthFCPropertyChangeListener();
307: }
308:
309: private class SynthFCPropertyChangeListener implements
310: PropertyChangeListener {
311: public void propertyChange(PropertyChangeEvent e) {
312: String prop = e.getPropertyName();
313: if (prop
314: .equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
315: doFileSelectionModeChanged(e);
316: } else if (prop
317: .equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
318: doSelectedFileChanged(e);
319: } else if (prop
320: .equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
321: doSelectedFilesChanged(e);
322: } else if (prop
323: .equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
324: doDirectoryChanged(e);
325: } else if (prop == JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY) {
326: doMultiSelectionChanged(e);
327: } else if (prop == JFileChooser.ACCESSORY_CHANGED_PROPERTY) {
328: doAccessoryChanged(e);
329: } else if (prop == JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY
330: || prop == JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY
331: || prop == JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY
332: || prop == JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY) {
333: doControlButtonsChanged(e);
334: } else if (prop.equals("componentOrientation")) {
335: ComponentOrientation o = (ComponentOrientation) e
336: .getNewValue();
337: JFileChooser cc = (JFileChooser) e.getSource();
338: if (o != (ComponentOrientation) e.getOldValue()) {
339: cc.applyComponentOrientation(o);
340: }
341: } else if (prop.equals("ancestor")) {
342: doAncestorChanged(e);
343: }
344: }
345: }
346:
347: /**
348: * Responds to a File Name completion request (e.g. Tab)
349: */
350: private class FileNameCompletionAction extends AbstractAction {
351: protected FileNameCompletionAction() {
352: super ("fileNameCompletion");
353: }
354:
355: public void actionPerformed(ActionEvent e) {
356: JFileChooser chooser = getFileChooser();
357:
358: String fileName = getFileName();
359:
360: if (fileName != null) {
361: // Remove whitespace from beginning and end of filename
362: fileName = fileName.trim();
363: }
364:
365: resetGlobFilter();
366:
367: if (fileName == null
368: || fileName.equals("")
369: || (chooser.isMultiSelectionEnabled() && fileName
370: .startsWith("\""))) {
371: return;
372: }
373:
374: FileFilter currentFilter = chooser.getFileFilter();
375: if (globFilter == null) {
376: globFilter = new GlobFilter();
377: }
378: try {
379: globFilter
380: .setPattern(!isGlobPattern(fileName) ? fileName
381: + "*" : fileName);
382: if (!(currentFilter instanceof GlobFilter)) {
383: actualFileFilter = currentFilter;
384: }
385: chooser.setFileFilter(null);
386: chooser.setFileFilter(globFilter);
387: fileNameCompletionString = fileName;
388: } catch (PatternSyntaxException pse) {
389: // Not a valid glob pattern. Abandon filter.
390: }
391: }
392: }
393:
394: private String fileNameCompletionString;
395:
396: private void updateFileNameCompletion() {
397: if (fileNameCompletionString != null) {
398: if (fileNameCompletionString.equals(getFileName())) {
399: File[] files = (File[]) getModel().getFiles().toArray(
400: new File[0]);
401: String str = getCommonStartString(files);
402: if (str != null
403: && str.startsWith(fileNameCompletionString)) {
404: setFileName(str);
405: }
406: fileNameCompletionString = null;
407: }
408: }
409: }
410:
411: private String getCommonStartString(File[] files) {
412: String str = null;
413: String str2 = null;
414: int i = 0;
415: if (files.length == 0) {
416: return null;
417: }
418: while (true) {
419: for (int f = 0; f < files.length; f++) {
420: String name = files[f].getName();
421: if (f == 0) {
422: if (name.length() == i) {
423: return str;
424: }
425: str2 = name.substring(0, i + 1);
426: }
427: if (!name.startsWith(str2)) {
428: return str;
429: }
430: }
431: str = str2;
432: i++;
433: }
434: }
435:
436: private void resetGlobFilter() {
437: if (actualFileFilter != null) {
438: JFileChooser chooser = getFileChooser();
439: FileFilter currentFilter = chooser.getFileFilter();
440: if (currentFilter != null
441: && currentFilter.equals(globFilter)) {
442: chooser.setFileFilter(actualFileFilter);
443: chooser.removeChoosableFileFilter(globFilter);
444: }
445: actualFileFilter = null;
446: }
447: }
448:
449: private static boolean isGlobPattern(String fileName) {
450: return ((File.separatorChar == '\\' && fileName.indexOf('*') >= 0) || (File.separatorChar == '/' && (fileName
451: .indexOf('*') >= 0
452: || fileName.indexOf('?') >= 0 || fileName.indexOf('[') >= 0)));
453: }
454:
455: /* A file filter which accepts file patterns containing
456: * the special wildcard '*' on windows, plus '?', and '[ ]' on Unix.
457: */
458: class GlobFilter extends FileFilter {
459: Pattern pattern;
460: String globPattern;
461:
462: public void setPattern(String globPattern) {
463: char[] gPat = globPattern.toCharArray();
464: char[] rPat = new char[gPat.length * 2];
465: boolean isWin32 = (File.separatorChar == '\\');
466: boolean inBrackets = false;
467: StringBuffer buf = new StringBuffer();
468: int j = 0;
469:
470: this .globPattern = globPattern;
471:
472: if (isWin32) {
473: // On windows, a pattern ending with *.* is equal to ending with *
474: int len = gPat.length;
475: if (globPattern.endsWith("*.*")) {
476: len -= 2;
477: }
478: for (int i = 0; i < len; i++) {
479: if (gPat[i] == '*') {
480: rPat[j++] = '.';
481: }
482: rPat[j++] = gPat[i];
483: }
484: } else {
485: for (int i = 0; i < gPat.length; i++) {
486: switch (gPat[i]) {
487: case '*':
488: if (!inBrackets) {
489: rPat[j++] = '.';
490: }
491: rPat[j++] = '*';
492: break;
493:
494: case '?':
495: rPat[j++] = inBrackets ? '?' : '.';
496: break;
497:
498: case '[':
499: inBrackets = true;
500: rPat[j++] = gPat[i];
501:
502: if (i < gPat.length - 1) {
503: switch (gPat[i + 1]) {
504: case '!':
505: case '^':
506: rPat[j++] = '^';
507: i++;
508: break;
509:
510: case ']':
511: rPat[j++] = gPat[++i];
512: break;
513: }
514: }
515: break;
516:
517: case ']':
518: rPat[j++] = gPat[i];
519: inBrackets = false;
520: break;
521:
522: case '\\':
523: if (i == 0 && gPat.length > 1 && gPat[1] == '~') {
524: rPat[j++] = gPat[++i];
525: } else {
526: rPat[j++] = '\\';
527: if (i < gPat.length - 1
528: && "*?[]".indexOf(gPat[i + 1]) >= 0) {
529: rPat[j++] = gPat[++i];
530: } else {
531: rPat[j++] = '\\';
532: }
533: }
534: break;
535:
536: default:
537: //if ("+()|^$.{}<>".indexOf(gPat[i]) >= 0) {
538: if (!Character.isLetterOrDigit(gPat[i])) {
539: rPat[j++] = '\\';
540: }
541: rPat[j++] = gPat[i];
542: break;
543: }
544: }
545: }
546: this .pattern = Pattern.compile(new String(rPat, 0, j),
547: Pattern.CASE_INSENSITIVE);
548: }
549:
550: public boolean accept(File f) {
551: if (f == null) {
552: return false;
553: }
554: if (f.isDirectory()) {
555: return true;
556: }
557: return pattern.matcher(f.getName()).matches();
558: }
559:
560: public String getDescription() {
561: return globPattern;
562: }
563: }
564:
565: // *******************************************************
566: // ************ FileChooser UI PLAF methods **************
567: // *******************************************************
568:
569: // *****************************
570: // ***** Directory Actions *****
571: // *****************************
572:
573: public Action getFileNameCompletionAction() {
574: return fileNameCompletionAction;
575: }
576:
577: protected JButton getApproveButton(JFileChooser fc) {
578: return approveButton;
579: }
580:
581: protected JButton getCancelButton(JFileChooser fc) {
582: return cancelButton;
583: }
584:
585: // Overload to do nothing. We don't have and icon cache.
586: public void clearIconCache() {
587: }
588:
589: // Copied as SynthBorder is package private in synth
590: private class UIBorder extends AbstractBorder implements UIResource {
591: private Insets _insets;
592:
593: UIBorder(Insets insets) {
594: if (insets != null) {
595: _insets = new Insets(insets.top, insets.left,
596: insets.bottom, insets.right);
597: } else {
598: _insets = null;
599: }
600: }
601:
602: public void paintBorder(Component c, Graphics g, int x, int y,
603: int width, int height) {
604: JComponent jc = (JComponent) c;
605: SynthContext context = getContext(jc);
606: SynthStyle style = context.getStyle();
607: if (style != null) {
608: style.getPainter(context).paintFileChooserBorder(
609: context, g, x, y, width, height);
610: }
611: }
612:
613: public Insets getBorderInsets(Component c) {
614: return getBorderInsets(c, null);
615: }
616:
617: public Insets getBorderInsets(Component c, Insets insets) {
618: if (insets == null) {
619: insets = new Insets(0, 0, 0, 0);
620: }
621: if (_insets != null) {
622: insets.top = _insets.top;
623: insets.bottom = _insets.bottom;
624: insets.left = _insets.left;
625: insets.right = _insets.right;
626: } else {
627: insets.top = insets.bottom = insets.right = insets.left = 0;
628: }
629: return insets;
630: }
631:
632: public boolean isBorderOpaque() {
633: return false;
634: }
635: }
636: }
|