// This file is part of Penn TotalRecall <http://memory.psych.upenn.edu/TotalRecall>. // // TotalRecall is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3 only. // // TotalRecall 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 for more details. // // You should have received a copy of the GNU General Public License // along with TotalRecall. If not, see <http://www.gnu.org/licenses/>. package components.preferences; import info.GUIConstants; import info.SysInfo; import info.UserPrefs; import java.awt.Component; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.util.ArrayList; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.border.Border; import util.GUIUtils; import components.MyFrame; /** * A <code>JFrame</code> that will contain preferences choosers in a scrollable vertical column. * * See package-level documentation for information on adding new preference choosers. * * @author Yuvi Masory */ public class PreferencesFrame extends JFrame implements WindowListener { private static PreferencesFrame instance; private JPanel prefPanel; private JScrollPane prefScrollPane; private JPanel buttonPanel; /** * Constructs a <code>PreferencesFrame</code> and initializes spacing behavior of its internal components. * * Adds all the known <code>AbstractPreferenceDisplays</code>. * Also adds "save" and "restore defaults" buttons to the end of the frame that are hooked up to the individual preference choosers. * * <p>See documentation in {@link preferences.AbstractPreferenceDisplay} for program-wide policies on user preferences, as well as information * on adding new preference choosers. */ private PreferencesFrame() { //force handling by the WindowListener (this.windowClosing()) setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); addWindowListener(this); setTitle(SysInfo.sys.preferencesString); //the content pane will have two main areas, one for preference choosers (AbstractPreferenceDisplays), and one for the save/restore defaults buttons JPanel contentPane = new JPanel(); contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); setContentPane(contentPane); initPrefPanel(); //to ensure correct resizing behavior from all the AbstractPreferenceDisplays we make them prefer a maximum width and their preferred height for(int i = 0; i < prefPanel.getComponentCount(); i++) { Component c = prefPanel.getComponent(i); if(c instanceof AbstractPreferenceDisplay) { c.setMaximumSize(new Dimension(Integer.MAX_VALUE, (int) c.getPreferredSize().getHeight())); } } initButtonsPanel(); //since there may be too many preferences in the prefPanel to fit in the PreferencesFrame, we add prefPanel to a scroll pane prefScrollPane = new JScrollPane(); prefScrollPane.setViewportView(prefPanel); prefScrollPane.getHorizontalScrollBar().setUnitIncrement(15); prefScrollPane.getVerticalScrollBar().setUnitIncrement(15); contentPane.add(prefScrollPane); contentPane.add(buttonPanel); //gets ride of the java icon in the top left corner of the frame (Windows, GNOME, KDE, among others) setIconImage(Toolkit.getDefaultToolkit().getImage( MyFrame.class.getResource("/images/headphones16.png"))); //the frame's width should be big enough for the button panel and big enough for even the largest AbstractPreferenceDisplay int buttonPanelPrefferedSize = (int) buttonPanel.getPreferredSize().getWidth(); int frameWidth = Math.max((int) prefScrollPane.getPreferredSize().getWidth(), buttonPanelPrefferedSize); int frameHeight = (int) (((double)2/(double)3) * (MyFrame.getInstance().getSize().getHeight())); setSize(frameWidth + 40, frameHeight); //let escape button be used to request preferences window closed contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false), "exit"); contentPane.getActionMap().put("exit", new AbstractAction(){ public void actionPerformed(ActionEvent e) { instance.windowClosing(new WindowEvent(instance, WindowEvent.WINDOW_CLOSING)); } }); } /** * Add all preference displays to the preferences panel. */ private void initPrefPanel() { //the panel that the AbstractPreferenceDisplays will be added to prefPanel = new JPanel(); prefPanel.setLayout(new BoxLayout(prefPanel, BoxLayout.Y_AXIS)); // prefPanel.add(new AudioSystemPreference()); prefPanel.add(new SeekSizePreference("Small Seek (ms)", SeekSizePreference.ShiftSize.SMALL_SHIFT)); prefPanel.add(new SeekSizePreference("Medium Seek (ms)", SeekSizePreference.ShiftSize.MEDIUM_SHIFT)); prefPanel.add(new SeekSizePreference("Large Seek (ms)", SeekSizePreference.ShiftSize.LARGE_SHIFT)); BandPassFilterPreference bandPref = new BandPassFilterPreference("Band-Pass Filter Range"); prefPanel.add(bandPref); BooleanPreference warnExitPref = new BooleanPreference("Warn on Exit", UserPrefs.warnExit, "Yes", "No", UserPrefs.defaultWarnExit); prefPanel.add(warnExitPref); BooleanPreference warnSwitchPref = new BooleanPreference("Warn on File Switch", UserPrefs.warnFileSwitch, "Yes", "No", UserPrefs.defaultWarnFileSwitch); prefPanel.add(warnSwitchPref); if(SysInfo.sys.isMacOSX == false) { BooleanPreference useEmacs = new BooleanPreference("Use Emacs Keybindings", UserPrefs.useEmacs, "Yes", "No", UserPrefs.defaultUseEmacs); prefPanel.add(useEmacs); } //excess space between the preference choosers and the buttons should not cause the preferences to move prefPanel.add(Box.createVerticalGlue()); } /** * Initializes the restore defaults/save prefs button area. */ private void initButtonsPanel() { //the panel that will contain the save/restore defaults buttons buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); buttonPanel.add(Box.createHorizontalGlue()); //glue added before and after every button so they will evenly take up all the width of the frame JButton jbSavePrefs = new JButton("Save Preferences"); jbSavePrefs.addActionListener(new SavePreferencesAction()); buttonPanel.add(jbSavePrefs); buttonPanel.add(Box.createHorizontalGlue()); JButton jbRestoreDefaults = new JButton("Restore Defaults"); jbRestoreDefaults.addActionListener(new RestoreDefaultsAction()); buttonPanel.add(jbRestoreDefaults); buttonPanel.add(Box.createHorizontalGlue()); //create some borders for the button panel to grab some space and be pretty Border outsideBorder = BorderFactory.createCompoundBorder( BorderFactory.createRaisedBevelBorder(), BorderFactory.createLoweredBevelBorder()); Border insideBorder = BorderFactory.createEmptyBorder(9, 5, 5, 5); buttonPanel.setBorder(BorderFactory.createCompoundBorder(outsideBorder, insideBorder)); } /** * Every time the frame is set to visible, it should appear in the middle of MyFrame, the app's main window. */ @Override public void setVisible(boolean visible) { if(visible == true) { setLocation(GUIUtils.chooseLocation(this)); } super.setVisible(visible); } /** * Getter for the displayed <code>AbstractPreferenceDisplays</code> for the benefit of <code>SavePreferencesAction</code> and others. * * @return All the <code>AbstractPreferenceDisplays</code> that are nested in any level in this <code>PreferencesFrame</code> */ protected ArrayList<AbstractPreferenceDisplay> getAbstractPreferences() { ArrayList<AbstractPreferenceDisplay> allPrefs = new ArrayList<AbstractPreferenceDisplay>(); Component[] allComps = prefPanel.getComponents(); for(int i = 0; i < allComps.length; i++) { if(allComps[i] instanceof AbstractPreferenceDisplay) { allPrefs.add((AbstractPreferenceDisplay) allComps[i]); } } return allPrefs; } /** * Handler for a user request to close the window (e.g., clicking on the frame's x button. * * After verifying that the user has not made any changes to the preferences, makes the frame invisible. * However, if changes are detected, the user will be warned that preferences will be lost (reverted to saved values) if he/she decides to exit. */ public void windowClosing(WindowEvent e) { ArrayList<AbstractPreferenceDisplay> allPrefs = PreferencesFrame.getInstance().getAbstractPreferences(); boolean somethingChanged = false; for(AbstractPreferenceDisplay pref: allPrefs) { if(pref.isChanged() == true) { somethingChanged = true; } } if(somethingChanged) { String message = "One or more preferences have changed.\nAre you sure you want to exit and lose your changes?"; int response = JOptionPane.showConfirmDialog(MyFrame.getInstance(), message, GUIConstants.yesNoDialogTitle, JOptionPane.YES_NO_OPTION); if(response == JOptionPane.YES_OPTION) { for(AbstractPreferenceDisplay pref: allPrefs) { pref.graphicallyRevert(); } setVisible(false); } else { toFront(); //to make sure PreferenceFrame will be in foreground return; } } else { setVisible(false); } } /** empty implementation {@inheritDoc}*/ public void windowActivated(WindowEvent e) {} /** empty implementation {@inheritDoc}*/ public void windowClosed(WindowEvent e) {} /** empty implementation {@inheritDoc}*/ public void windowDeactivated(WindowEvent e) {} /** empty implementation {@inheritDoc}*/ public void windowDeiconified(WindowEvent e) {} /** empty implementation {@inheritDoc}*/ public void windowIconified(WindowEvent e) {} /** empty implementation {@inheritDoc}*/ public void windowOpened(WindowEvent e) {} /** * Singleton accessor * * @return The singleton <code>PreferencesFrame</code> */ public static PreferencesFrame getInstance() { if(instance == null) { instance= new PreferencesFrame(); } return instance; } }