/*
* Copyright 2002-2005 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package sun.swing.plaf.synth;
import javax.swing.plaf.synth.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.File;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.filechooser.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicFileChooserUI;
/**
* Synth FileChooserUI.
*
* Note: This class is abstract. It does not actually create the file chooser GUI.
* <p>
* Note that the classes in the com.sun.java.swing.plaf.synth
* package are not
* part of the core Java APIs. They are a part of Sun's JDK and JRE
* distributions. Although other licensees may choose to distribute
* these classes, developers cannot depend on their availability in
* non-Sun implementations. Additionally this API may change in
* incompatible ways between releases. While this class is public, it
* shoud be considered an implementation detail, and subject to change.
*
* @author Leif Samuelsson
* @author Jeff Dinkins
*/
public abstract class SynthFileChooserUI extends BasicFileChooserUI implements
SynthUI {
private JButton approveButton, cancelButton;
private SynthStyle style;
// Some generic FileChooser functions
private Action fileNameCompletionAction = new FileNameCompletionAction();
private FileFilter actualFileFilter = null;
private GlobFilter globFilter = null;
public static ComponentUI createUI(JComponent c) {
return new SynthFileChooserUIImpl((JFileChooser)c);
}
public SynthFileChooserUI(JFileChooser b) {
super(b);
}
public SynthContext getContext(JComponent c) {
return new SynthContext(c, Region.FILE_CHOOSER, style,
getComponentState(c));
}
protected SynthContext getContext(JComponent c, int state) {
Region region = SynthLookAndFeel.getRegion(c);
return new SynthContext(c, Region.FILE_CHOOSER, style, state);
}
private Region getRegion(JComponent c) {
return SynthLookAndFeel.getRegion(c);
}
private int getComponentState(JComponent c) {
if (c.isEnabled()) {
if (c.isFocusOwner()) {
return ENABLED | FOCUSED;
}
return ENABLED;
}
return DISABLED;
}
private void updateStyle(JComponent c) {
SynthStyle newStyle = SynthLookAndFeel.getStyleFactory().getStyle(c,
Region.FILE_CHOOSER);
if (newStyle != style) {
if (style != null) {
style.uninstallDefaults(getContext(c, ENABLED));
}
style = newStyle;
SynthContext context = getContext(c, ENABLED);
style.installDefaults(context);
Border border = c.getBorder();
if (border == null || border instanceof UIResource) {
c.setBorder(new UIBorder(style.getInsets(context, null)));
}
directoryIcon = style.getIcon(context, "FileView.directoryIcon");
fileIcon = style.getIcon(context, "FileView.fileIcon");
computerIcon = style.getIcon(context, "FileView.computerIcon");
hardDriveIcon = style.getIcon(context, "FileView.hardDriveIcon");
floppyDriveIcon = style.getIcon(context, "FileView.floppyDriveIcon");
newFolderIcon = style.getIcon(context, "FileChooser.newFolderIcon");
upFolderIcon = style.getIcon(context, "FileChooser.upFolderIcon");
homeFolderIcon = style.getIcon(context, "FileChooser.homeFolderIcon");
detailsViewIcon = style.getIcon(context, "FileChooser.detailsViewIcon");
listViewIcon = style.getIcon(context, "FileChooser.listViewIcon");
}
}
public void installUI(JComponent c) {
super.installUI(c);
SwingUtilities.replaceUIActionMap(c, createActionMap());
}
public void installComponents(JFileChooser fc) {
SynthContext context = getContext(fc, ENABLED);
cancelButton = new JButton(cancelButtonText);
cancelButton.setName("SynthFileChooser.cancelButton");
cancelButton.setIcon(context.getStyle().getIcon(context, "FileChooser.cancelIcon"));
cancelButton.setMnemonic(cancelButtonMnemonic);
cancelButton.setToolTipText(cancelButtonToolTipText);
cancelButton.addActionListener(getCancelSelectionAction());
approveButton = new JButton(getApproveButtonText(fc));
approveButton.setName("SynthFileChooser.approveButton");
approveButton.setIcon(context.getStyle().getIcon(context, "FileChooser.okIcon"));
approveButton.setMnemonic(getApproveButtonMnemonic(fc));
approveButton.setToolTipText(getApproveButtonToolTipText(fc));
approveButton.addActionListener(getApproveSelectionAction());
}
public void uninstallComponents(JFileChooser fc) {
fc.removeAll();
}
protected void installListeners(JFileChooser fc) {
super.installListeners(fc);
getModel().addListDataListener(new ListDataListener() {
public void contentsChanged(ListDataEvent e) {
// Update the selection after JList has been updated
new DelayedSelectionUpdater();
}
public void intervalAdded(ListDataEvent e) {
new DelayedSelectionUpdater();
}
public void intervalRemoved(ListDataEvent e) {
}
});
}
private class DelayedSelectionUpdater implements Runnable {
DelayedSelectionUpdater() {
SwingUtilities.invokeLater(this);
}
public void run() {
updateFileNameCompletion();
}
}
protected abstract ActionMap createActionMap();
protected void installDefaults(JFileChooser fc) {
super.installDefaults(fc);
updateStyle(fc);
}
protected void uninstallDefaults(JFileChooser fc) {
super.uninstallDefaults(fc);
SynthContext context = getContext(getFileChooser(), ENABLED);
style.uninstallDefaults(context);
style = null;
}
protected void installIcons(JFileChooser fc) {
// The icons are installed in updateStyle, not here
}
public void update(Graphics g, JComponent c) {
SynthContext context = getContext(c);
if (c.isOpaque()) {
g.setColor(style.getColor(context, ColorType.BACKGROUND));
g.fillRect(0, 0, c.getWidth(), c.getHeight());
}
style.getPainter(context).paintFileChooserBackground(context,
g, 0, 0, c.getWidth(), c.getHeight());
paint(context, g);
}
public void paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h) {
}
public void paint(Graphics g, JComponent c) {
SynthContext context = getContext(c);
paint(context, g);
}
protected void paint(SynthContext context, Graphics g) {
}
abstract public void setFileName(String fileName);
abstract public String getFileName();
protected void doSelectedFileChanged(PropertyChangeEvent e) {
}
protected void doSelectedFilesChanged(PropertyChangeEvent e) {
}
protected void doDirectoryChanged(PropertyChangeEvent e) {
}
protected void doAccessoryChanged(PropertyChangeEvent e) {
}
protected void doFileSelectionModeChanged(PropertyChangeEvent e) {
}
protected void doMultiSelectionChanged(PropertyChangeEvent e) {
if (!getFileChooser().isMultiSelectionEnabled()) {
getFileChooser().setSelectedFiles(null);
}
}
protected void doControlButtonsChanged(PropertyChangeEvent e) {
if (getFileChooser().getControlButtonsAreShown()) {
approveButton.setText(getApproveButtonText(getFileChooser()));
approveButton.setToolTipText(getApproveButtonToolTipText(getFileChooser()));
}
}
protected void doAncestorChanged(PropertyChangeEvent e) {
}
public PropertyChangeListener createPropertyChangeListener(JFileChooser fc) {
return new SynthFCPropertyChangeListener();
}
private class SynthFCPropertyChangeListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
String prop = e.getPropertyName();
if (prop.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)) {
doFileSelectionModeChanged(e);
} else if (prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
doSelectedFileChanged(e);
} else if (prop.equals(JFileChooser.SELECTED_FILES_CHANGED_PROPERTY)) {
doSelectedFilesChanged(e);
} else if (prop.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
doDirectoryChanged(e);
} else if (prop == JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY) {
doMultiSelectionChanged(e);
} else if (prop == JFileChooser.ACCESSORY_CHANGED_PROPERTY) {
doAccessoryChanged(e);
} else if (prop == JFileChooser.APPROVE_BUTTON_TEXT_CHANGED_PROPERTY ||
prop == JFileChooser.APPROVE_BUTTON_TOOL_TIP_TEXT_CHANGED_PROPERTY ||
prop == JFileChooser.DIALOG_TYPE_CHANGED_PROPERTY ||
prop == JFileChooser.CONTROL_BUTTONS_ARE_SHOWN_CHANGED_PROPERTY) {
doControlButtonsChanged(e);
} else if (prop.equals("componentOrientation")) {
ComponentOrientation o = (ComponentOrientation)e.getNewValue();
JFileChooser cc = (JFileChooser)e.getSource();
if (o != (ComponentOrientation)e.getOldValue()) {
cc.applyComponentOrientation(o);
}
} else if (prop.equals("ancestor")) {
doAncestorChanged(e);
}
}
}
/**
* Responds to a File Name completion request (e.g. Tab)
*/
private class FileNameCompletionAction extends AbstractAction {
protected FileNameCompletionAction() {
super("fileNameCompletion");
}
public void actionPerformed(ActionEvent e) {
JFileChooser chooser = getFileChooser();
String fileName = getFileName();
if (fileName != null) {
// Remove whitespace from beginning and end of filename
fileName = fileName.trim();
}
resetGlobFilter();
if (fileName == null || fileName.equals("") ||
(chooser.isMultiSelectionEnabled() && fileName.startsWith("\""))) {
return;
}
FileFilter currentFilter = chooser.getFileFilter();
if (globFilter == null) {
globFilter = new GlobFilter();
}
try {
globFilter.setPattern(!isGlobPattern(fileName) ? fileName + "*" : fileName);
if (!(currentFilter instanceof GlobFilter)) {
actualFileFilter = currentFilter;
}
chooser.setFileFilter(null);
chooser.setFileFilter(globFilter);
fileNameCompletionString = fileName;
} catch (PatternSyntaxException pse) {
// Not a valid glob pattern. Abandon filter.
}
}
}
private String fileNameCompletionString;
private void updateFileNameCompletion() {
if (fileNameCompletionString != null) {
if (fileNameCompletionString.equals(getFileName())) {
File[] files = getModel().getFiles().toArray(new File[0]);
String str = getCommonStartString(files);
if (str != null && str.startsWith(fileNameCompletionString)) {
setFileName(str);
}
fileNameCompletionString = null;
}
}
}
private String getCommonStartString(File[] files) {
String str = null;
String str2 = null;
int i = 0;
if (files.length == 0) {
return null;
}
while (true) {
for (int f = 0; f < files.length; f++) {
String name = files[f].getName();
if (f == 0) {
if (name.length() == i) {
return str;
}
str2 = name.substring(0, i+1);
}
if (!name.startsWith(str2)) {
return str;
}
}
str = str2;
i++;
}
}
private void resetGlobFilter() {
if (actualFileFilter != null) {
JFileChooser chooser = getFileChooser();
FileFilter currentFilter = chooser.getFileFilter();
if (currentFilter != null && currentFilter.equals(globFilter)) {
chooser.setFileFilter(actualFileFilter);
chooser.removeChoosableFileFilter(globFilter);
}
actualFileFilter = null;
}
}
private static boolean isGlobPattern(String fileName) {
return ((File.separatorChar == '\\' && fileName.indexOf('*') >= 0)
|| (File.separatorChar == '/' && (fileName.indexOf('*') >= 0
|| fileName.indexOf('?') >= 0
|| fileName.indexOf('[') >= 0)));
}
/* A file filter which accepts file patterns containing
* the special wildcard '*' on windows, plus '?', and '[ ]' on Unix.
*/
class GlobFilter extends FileFilter {
Pattern pattern;
String globPattern;
public void setPattern(String globPattern) {
char[] gPat = globPattern.toCharArray();
char[] rPat = new char[gPat.length * 2];
boolean isWin32 = (File.separatorChar == '\\');
boolean inBrackets = false;
int j = 0;
this.globPattern = globPattern;
if (isWin32) {
// On windows, a pattern ending with *.* is equal to ending with *
int len = gPat.length;
if (globPattern.endsWith("*.*")) {
len -= 2;
}
for (int i = 0; i < len; i++) {
if (gPat[i] == '*') {
rPat[j++] = '.';
}
rPat[j++] = gPat[i];
}
} else {
for (int i = 0; i < gPat.length; i++) {
switch(gPat[i]) {
case '*':
if (!inBrackets) {
rPat[j++] = '.';
}
rPat[j++] = '*';
break;
case '?':
rPat[j++] = inBrackets ? '?' : '.';
break;
case '[':
inBrackets = true;
rPat[j++] = gPat[i];
if (i < gPat.length - 1) {
switch (gPat[i+1]) {
case '!':
case '^':
rPat[j++] = '^';
i++;
break;
case ']':
rPat[j++] = gPat[++i];
break;
}
}
break;
case ']':
rPat[j++] = gPat[i];
inBrackets = false;
break;
case '\\':
if (i == 0 && gPat.length > 1 && gPat[1] == '~') {
rPat[j++] = gPat[++i];
} else {
rPat[j++] = '\\';
if (i < gPat.length - 1 && "*?[]".indexOf(gPat[i+1]) >= 0) {
rPat[j++] = gPat[++i];
} else {
rPat[j++] = '\\';
}
}
break;
default:
//if ("+()|^$.{}<>".indexOf(gPat[i]) >= 0) {
if (!Character.isLetterOrDigit(gPat[i])) {
rPat[j++] = '\\';
}
rPat[j++] = gPat[i];
break;
}
}
}
this.pattern = Pattern.compile(new String(rPat, 0, j), Pattern.CASE_INSENSITIVE);
}
public boolean accept(File f) {
if (f == null) {
return false;
}
if (f.isDirectory()) {
return true;
}
return pattern.matcher(f.getName()).matches();
}
public String getDescription() {
return globPattern;
}
}
// *******************************************************
// ************ FileChooser UI PLAF methods **************
// *******************************************************
// *****************************
// ***** Directory Actions *****
// *****************************
public Action getFileNameCompletionAction() {
return fileNameCompletionAction;
}
protected JButton getApproveButton(JFileChooser fc) {
return approveButton;
}
protected JButton getCancelButton(JFileChooser fc) {
return cancelButton;
}
// Overload to do nothing. We don't have and icon cache.
public void clearIconCache() { }
// Copied as SynthBorder is package private in synth
private class UIBorder extends AbstractBorder implements UIResource {
private Insets _insets;
UIBorder(Insets insets) {
if (insets != null) {
_insets = new Insets(insets.top, insets.left, insets.bottom,
insets.right);
}
else {
_insets = null;
}
}
public void paintBorder(Component c, Graphics g, int x, int y,
int width, int height) {
JComponent jc = (JComponent)c;
SynthContext context = getContext(jc);
SynthStyle style = context.getStyle();
if (style != null) {
style.getPainter(context).paintFileChooserBorder(
context, g, x, y, width, height);
}
}
public Insets getBorderInsets(Component c, Insets insets) {
if (insets == null) {
insets = new Insets(0, 0, 0, 0);
}
if (_insets != null) {
insets.top = _insets.top;
insets.bottom = _insets.bottom;
insets.left = _insets.left;
insets.right = _insets.right;
}
else {
insets.top = insets.bottom = insets.right = insets.left = 0;
}
return insets;
}
public boolean isBorderOpaque() {
return false;
}
}
}