danBhentschel
danBhentschel

Reputation: 883

Save the state of a JFileChooser

A user has asked that I preserve the state of the JFileChooser across application restarts. Specifically, he has asked that I preserve the state of the Details / List view type selection. Two applicable questions:

How can I start the JFileChooser in the Details view?

Start a JFileChooser with files ordered by date

These both show methods of starting the JFileChooser with specific default behavior. The piece that is missing is a way to determine what behavior the user had active (view type, sort order) when the JFileChooser window is closed, so that it can be saved and restored later. Any ideas?

Upvotes: 3

Views: 799

Answers (2)

danBhentschel
danBhentschel

Reputation: 883

Based on feedback, I have created the following class. I believe that this gives all the functionality I am looking for. This class depends on this SwingUtils class.

You will also need to update access restriction rules (at least in Eclipse) to allow access to sun/swing/FilePane as described in

Access restriction: Is not accessible due to restriction on required library ..\jre\lib\rt.jar

Edit: Cleanup resource leak

After coming back to work from a long vacation, I realized that the code I initially provided could leak JFileChooser instances, and so I have reworked it to provide an AutoCloseable entity that can be used in a try-with-resources statement. Sorry for the churn.

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.Preferences;

import javax.swing.Action;
import javax.swing.JFileChooser;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.RowSorter.SortKey;
import javax.swing.SortOrder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sun.swing.FilePane;
import darrylbu.util.SwingUtils;

@SuppressWarnings("restriction")
public class JFileChooserPersisterFactory {
    private static final String VIEW_TYPE_LIST = "viewTypeList"; //$NON-NLS-1$
    private static final String VIEW_TYPE_DETAILS = "viewTypeDetails"; //$NON-NLS-1$
    private static final String CHOOSER_CLOSING_PROPERTY = "JFileChooserDialogIsClosingProperty"; //$NON-NLS-1$
    private static final String VIEW_TYPE_PROPERTY = "viewType"; //$NON-NLS-1$
    private static final String IS_DETAILS = "isDetails"; //$NON-NLS-1$
    private static final String SORT_ORDER = "sortOrder"; //$NON-NLS-1$

    private JFileChooserPersisterFactory() {
    }

    public static JFileChooserPersister createJFileChooserPersister() {
        JFileChooserPersisterImpl persister = new JFileChooserPersisterImpl();
        persister.init();
        return persister;
    }

    public interface JFileChooserPersister extends AutoCloseable {
        JFileChooser getJFileChooser();

        @Override
        void close();
    }

    private static class JFileChooserPersisterImpl implements JFileChooserPersister {
        private final Logger logger = LoggerFactory.getLogger(getClass());
        private final Preferences persistentPrefs = Preferences.userNodeForPackage(getClass());

        private final JFileChooser chooser;
        private boolean isDetails;
        private OnChooserClosing chooserClosingListener;
        private FilePane filePane;
        private OnViewTypeChanged viewTypeChangedListener;

        public JFileChooserPersisterImpl() {
            chooser = new JFileChooser();
        }

        public void init() {
            restoreSettings();
            registerForViewTypeChangeEvents();
            chooserClosingListener = new OnChooserClosing();
            chooser.addPropertyChangeListener(CHOOSER_CLOSING_PROPERTY, chooserClosingListener);
        }

        @Override
        public JFileChooser getJFileChooser() {
            return chooser;
        }

        private void persistSettings() {
            persistentPrefs.putBoolean(IS_DETAILS, isDetails);
            if (isDetails) persistSortOrder();
        }

        private void persistSortOrder() {
            byte[] serializedSortOrder = serializeSortOrder();
            if (serializedSortOrder != null)
                persistentPrefs.putByteArray(SORT_ORDER, serializedSortOrder);
        }

        private byte[] serializeSortOrder() {
            List<? extends SortKey> keys = getRowSorter().getSortKeys();
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            try (ObjectOutputStream out = new ObjectOutputStream(byteStream)) {
                out.writeObject(new SortOrderInfo(keys));
                return byteStream.toByteArray();
            } catch (IOException e) {
                logger.error("Could not serialize JFileChooser row sort order.", e); //$NON-NLS-1$
            }
            return null;
        }

        private void restoreSettings() {
            isDetails = persistentPrefs.getBoolean(IS_DETAILS, false);
            if (isDetails) {
                setToDetailsView();
                applyInitialSortOrder();
            } else {
                setToListView();
            }
        }

        private void setToDetailsView() {
            Action details = chooser.getActionMap().get(VIEW_TYPE_DETAILS);
            details.actionPerformed(null);
        }

        private void setToListView() {
            Action details = chooser.getActionMap().get(VIEW_TYPE_LIST);
            details.actionPerformed(null);
        }

        private void applyInitialSortOrder() {
            byte[] serializedSortOrder = persistentPrefs.getByteArray(SORT_ORDER, null);
            if (serializedSortOrder == null) return;
            ByteArrayInputStream byteStream = new ByteArrayInputStream(serializedSortOrder);
            try (ObjectInputStream in = new ObjectInputStream(byteStream)) {
                setSortInfo((SortOrderInfo) in.readObject());

            } catch (IOException | ClassNotFoundException e) {
                logger.error("Could not deserialize JFileChooser row sort order.", e); //$NON-NLS-1$
            }
        }

        private void setSortInfo(SortOrderInfo info) {
            info.setSortOrder(getRowSorter());
        }

        private RowSorter<?> getRowSorter() {
            JTable table = SwingUtils.getDescendantsOfType(JTable.class, chooser).get(0);
            RowSorter<?> rowSorter = table.getRowSorter();
            return rowSorter;
        }

        private void registerForViewTypeChangeEvents() {
            filePane = SwingUtils.getDescendantsOfType(FilePane.class, chooser).get(0);
            viewTypeChangedListener = new OnViewTypeChanged();
            filePane.addPropertyChangeListener(VIEW_TYPE_PROPERTY, viewTypeChangedListener);
        }

        private final class OnChooserClosing implements PropertyChangeListener {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                persistSettings();
            }
        }

        private class OnViewTypeChanged implements PropertyChangeListener {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                isDetails = ((int) evt.getNewValue()) == FilePane.VIEWTYPE_DETAILS;
            }
        }

        public static class SortOrderInfo implements Serializable {
            private static final long serialVersionUID = -5393878644049680645L;
            private final List<ColumnSortInfo> keyInfo = new ArrayList<>();

            public SortOrderInfo(List<? extends SortKey> keys) {
                for (SortKey sortKey : keys) {
                    keyInfo.add(new ColumnSortInfo(sortKey));
                }
            }

            public void setSortOrder(RowSorter<?> rowSorter) {
                rowSorter.setSortKeys(makeSortKeys());
            }

            private List<SortKey> makeSortKeys() {
                List<SortKey> keys = new ArrayList<>();
                for (ColumnSortInfo info : keyInfo) {
                    keys.add(info.makeSortKey());
                }
                return keys;
            }

            public static class ColumnSortInfo implements Serializable {
                private static final long serialVersionUID = 5406885180955729893L;
                private final SortOrder sortOrder;
                private final int column;

                public ColumnSortInfo(SortKey sortKey) {
                    column = sortKey.getColumn();
                    sortOrder = sortKey.getSortOrder();
                }

                public SortKey makeSortKey() {
                    return new SortKey(column, sortOrder);
                }
            }
        }

        @Override
        public void close() {
            chooser.removePropertyChangeListener(CHOOSER_CLOSING_PROPERTY, chooserClosingListener);
            filePane.removePropertyChangeListener(VIEW_TYPE_PROPERTY, viewTypeChangedListener);
        }
    }
}

Upvotes: 1

camickr
camickr

Reputation: 324118

You can use the Properties API or Preferences API to save/restore user data.

  1. At start up you would read the users data and set the file chooser property.
  2. To listener for user changes to the view type you can add a PropertyChangeListener to the file chooser and listen for the viewType event. Then you would update the user data with the new value.

You can add a RowSorterListener to the RowSorter to listen for changes in the sort order. You would then need to save the sort order. I don't know the best way to store the sort data.

Upvotes: 3

Related Questions