views:

3405

answers:

4

I'm writing a diagram editor in java. This app has the option to export to various standard image formats such as .jpg, .png etc. When the user clicks File->Export, you get a JFileChooser which has a number of FileFilters in it, for .jpg, .png etc.

Now here is my question:

Is there a way to have the extension of the default adjust to the selected file filter? E.g. if the document is named "lolcat" then the default option should be "lolcat.png" when the png filter is selected, and when the user selects the jpg file filter, the default should change to "lolcat.jpg" automatically.

Is this possible? How can I do it?

edit: Based on the answer below, I wrote some code. But it doesn't quite work yet. I've added a propertyChangeListener to the FILE_FILTER_CHANGED_PROPERTY, but it seems that within this method getSelectedFile() returns null. Here is the code.

package nl.helixsoft;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.filechooser.FileFilter;

public class JFileChooserTest 
{
 public class SimpleFileFilter extends FileFilter
 {
  private String desc;
  private List<String> extensions;
  private boolean showDirectories;

  /**
   * @param name example: "Data files"
   * @param glob example: "*.txt|*.csv"
   */
  public SimpleFileFilter (String name, String globs) 
  {
   extensions = new ArrayList<String>();
   for (String glob : globs.split("\\|"))
   {
    if (!glob.startsWith("*.")) 
     throw new IllegalArgumentException("expected list of globs like \"*.txt|*.csv\"");
    // cut off "*"
    // store only lower case (make comparison case insensitive)
    extensions.add (glob.substring(1).toLowerCase());
   }
   desc = name + " (" + globs + ")";
  }

  public SimpleFileFilter(String name, String globs, boolean showDirectories) {
   this(name, globs);
   this.showDirectories = showDirectories;
  }

  @Override
  public boolean accept(File file) 
  {
   if(showDirectories && file.isDirectory()) {
    return true;
   }
   String fileName = file.toString().toLowerCase();

   for (String extension : extensions)
   { 
    if (fileName.endsWith (extension))
    {
     return true;
    }
   }
   return false;
  }

  @Override
  public String getDescription() 
  {
   return desc;
  }

  /**
   * @return includes '.'
   */
  public String getFirstExtension()
  {
   return extensions.get(0);
  }
 }

 void export()
 {
  String documentTitle = "lolcat";

  final JFileChooser jfc = new JFileChooser();
  jfc.setDialogTitle("Export");
  jfc.setDialogType(JFileChooser.SAVE_DIALOG);
  jfc.setSelectedFile(new File (documentTitle));
  jfc.addChoosableFileFilter(new SimpleFileFilter("JPEG", "*.jpg"));
  jfc.addChoosableFileFilter(new SimpleFileFilter("PNG", "*.png"));
  jfc.addPropertyChangeListener(JFileChooser.FILE_FILTER_CHANGED_PROPERTY, new PropertyChangeListener()
  {
   public void propertyChange(PropertyChangeEvent arg0) 
   {
    System.out.println ("Property changed");
    String extold = null;
    String extnew = null;
    if (arg0.getOldValue() == null || !(arg0.getOldValue() instanceof SimpleFileFilter)) return;
    if (arg0.getNewValue() == null || !(arg0.getNewValue() instanceof SimpleFileFilter)) return;
    SimpleFileFilter oldValue = ((SimpleFileFilter)arg0.getOldValue());
    SimpleFileFilter newValue = ((SimpleFileFilter)arg0.getNewValue());
    extold = oldValue.getFirstExtension();
    extnew = newValue.getFirstExtension();
    String filename = "" + jfc.getSelectedFile();
    System.out.println ("file: " + filename + " old: " + extold + ", new: " + extnew);
    if (filename.endsWith(extold))
    {
     filename.replace(extold, extnew);
    }
    else
    {
     filename += extnew;
    }
    jfc.setSelectedFile(new File (filename));
   }
  });
  jfc.showDialog(frame, "export");
 }

 JFrame frame;

 void run()
 {
  frame = new JFrame();
  JButton btn = new JButton ("export");
  frame.add (btn);
  btn.addActionListener (new ActionListener()
  {
   public void actionPerformed(ActionEvent ae)
   {
    export();
   }
  });
  frame.setSize (300, 300);
  frame.pack();
  frame.setVisible(true);
 }

 public static void main(String[] args)
 {
  javax.swing.SwingUtilities.invokeLater(new Runnable() 
  {  
   public void run() 
   {
    JFileChooserTest x =  new JFileChooserTest();
    x.run();
   }
  });  
 }
}
+5  A: 

It looks like you can listen to the JFileChooser for a change on the FILE_FILTER_CHANGED_PROPERTY property, then change the extension of the selected file appropriately using setSelectedFile().


EDIT: You're right, this solution doesn't work. It turns out that when the file filter is changed, the selected file is removed if its file type doesn't match the new filter. That's why you're getting the null when you try to getSelectedFile().

Have you considered adding the extension later? When I am writing a JFileChooser, I usually add the extension after the user has chosen a file to use and clicked "Save":

if (result == JFileChooser.APPROVE_OPTION)
{
  File file = fileChooser.getSelectedFile();
  String path = file.getAbsolutePath();

  String extension = getExtensionForFilter(fileChooser.getFileFilter());

  if(!path.endsWith(extension))
  {
    file = new File(path + extension);
  }
}


fileChooser.addPropertyChangeListener(JFileChooser.FILE_FILTER_CHANGED_PROPERTY, new PropertyChangeListener()
{
  public void propertyChange(PropertyChangeEvent evt)
  {
    FileFilter filter = (FileFilter)evt.getNewValue();

    String extension = getExtensionForFilter(filter); //write this method or some equivalent

    File selectedFile = fileChooser.getSelectedFile();
    String path = selectedFile.getAbsolutePath();
    path.substring(0, path.lastIndexOf("."));

    fileChooser.setSelectedFile(new File(path + extension));
  }
});
Amanda S
A: 

The use of getAbsolutePath() in the previous change the current directory. I was surprised when the JFileChooser dialog displaying "My documents" directory change to the Netbeans's project directory when I selected a different FileFilter, so I changed it to use getName(). I also used the JDK 6 FileNameExtensionFilter.

Here is the code:

    final JFileChooser fc = new JFileChooser();
    final File sFile = new File("test.xls");
    fc.setSelectedFile(sFile);
    // Store this filter in a variable to be able to select this after adding all FileFilter
    // because addChoosableFileFilter add FileFilter in order in the combo box
    final FileNameExtensionFilter excelFilter = new FileNameExtensionFilter("Excel document (*.xls)", "xls");
    fc.addChoosableFileFilter(excelFilter);
    fc.addChoosableFileFilter(new FileNameExtensionFilter("CSV document (*.csv)", "csv"));
    // Force the excel filter
    fc.setFileFilter(excelFilter);
    // Disable All Files
    fc.setAcceptAllFileFilterUsed(false);

    // debug
    fc.addPropertyChangeListener(new PropertyChangeListener() {

        public void propertyChange(PropertyChangeEvent evt) {
            System.out.println("Property name=" + evt.getPropertyName() + ", oldValue=" + evt.getOldValue() + ", newValue=" + evt.getNewValue());
            System.out.println("getSelectedFile()=" + fc.getSelectedFile());
        }
    });

    fc.addPropertyChangeListener(JFileChooser.FILE_FILTER_CHANGED_PROPERTY, new PropertyChangeListener() {

        public void propertyChange(PropertyChangeEvent evt) {
            Object o = evt.getNewValue();
            if (o instanceof FileNameExtensionFilter) {
                FileNameExtensionFilter filter = (FileNameExtensionFilter) o;

                String ex = filter.getExtensions()[0];

                File selectedFile = fc.getSelectedFile();
                if (selectedFile == null) {
                    selectedFile = sFile;
                }
                String path = selectedFile.getName();
                path = path.substring(0, path.lastIndexOf("."));

                fc.setSelectedFile(new File(path + "." + ex));
            }
        }
    });
Franck
A: 

Here's my attempt at this. It uses the accept() function to check whether or not the file passes the filter. If the filename does not, the extension is appended to the end.

JFileChooser jfc = new JFileChooser(getFile()) {
        public void approveSelection() {
            if (getDialogType() == SAVE_DIALOG) {
                File selectedFile = getSelectedFile();

                FileFilter ff = getFileFilter();

                // Checks against the current selected filter
                if (!ff.accept(selectedFile)) {
                    selectedFile = new File(selectedFile.getPath() + ".txt");
                }
                super.setSelectedFile(selectedFile);

                if ((selectedFile != null) && selectedFile.exists()) {
                    int response = JOptionPane.showConfirmDialog(
                            this,
                            "The file " + selectedFile.getName() + " already exists.\n" +
                            "Do you want to replace it?",
                            "Ovewrite file",
                            JOptionPane.YES_NO_OPTION,
                            JOptionPane.WARNING_MESSAGE
                    );
                    if (response == JOptionPane.NO_OPTION)
                        return;
                }
            }
            super.approveSelection();
        }
    };
Andy Doyle
+1  A: 

You can also use a PropertyChangeListener on the SELECTED_FILE_CHANGED_PROPERTY prior to attaching your suffix. When the selected file gets checked against the new filter (and subsequently set to null), the SELECTED_FILE_CHANGED_PROPERTY event is actually fired before the FILE_FILTER_CHANGED_PROPERTY event.

If the evt.getOldValue() != null and the evt.getNewValue() == null, you know that the JFileChooser has blasted your file. You can then grab the old file's name (using ((File)evt.getOldValue()).getName() as described above), pull off the extension using standard string parsing functions, and stash it into a named member variable within your class.

That way, when the FILE_FILTER_CHANGED event is triggered (immediately afterwards, as near as I can determine), you can pull that stashed root name from the named member variable, apply the extension for the new file filter type, and set the JFileChooser's selected file accordingly.

Marc Renouf