JAL-3141 Preferences 'Backups' tab now working. Doesn't fit in default space though...
authorBen Soares <bsoares@dundee.ac.uk>
Wed, 31 Oct 2018 17:04:19 +0000 (17:04 +0000)
committerBen Soares <bsoares@dundee.ac.uk>
Wed, 31 Oct 2018 17:04:19 +0000 (17:04 +0000)
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/gui/JalviewBooleanRadioButtons.java [new file with mode: 0644]
src/jalview/gui/Preferences.java
src/jalview/io/BackupFilenameFilter.java
src/jalview/io/BackupFiles.java
src/jalview/jbgui/GPreferences.java

index bcd63ae..f7cf83d 100644 (file)
@@ -1367,3 +1367,21 @@ label.backupfiles_confirm_delete = Confirm delete
 label.backupfiles_confirm_delete_old_files = Delete the following older version files?
 label.backupfiles_confirm_save_file = Confirm save file
 label.backupfiles_confirm_save_file_backupfiles_roll_wrong = Something possibly went wrong with the backups of this file, write the new file anyway?
+label.backups = Backups
+label.backup_files = Backup Files
+label.enable_backupfiles = Enable backup files
+label.suffix_format = Suffix format
+label.suffix_template = Suffix template (use %n to represent the backup/version index)
+label.suffix_template_tooltip = %n in the template will be replaced by the index number. The suffix will appear before the filename extension.
+label.index_digits = Number of digits to use for the index
+default.suffix_index_digits_min = 1
+default.suffix_index_digits_max = 6
+label.examples = Examples
+label.suffix_example_filenames = Some example filenames using these settings:
+label.increment_index = Increment filename indexes (like version numbers). Newest file has largest index.
+label.reverse_roll = Reverse and "roll" indexes (like rolled log files). Newest files is always indexed as 1.
+label.keep_files = Keep Files
+label.keep_all_backup_files = Keep all backup files
+label.keep_only_this_number_of_backup_files = Keep only this number of most recent backup files
+label.confirm_delete = Confirm deletion of old backup files
+label.auto_delete = Automatically delete old backup files
index 9498aaf..f3be98c 100644 (file)
@@ -1364,7 +1364,13 @@ label.most_bound_molecules = M
 label.most_polymer_residues = Más Residuos de Polímeros
 label.cached_structures = Estructuras en Caché
 label.free_text_search = Búsqueda de texto libre
+# dodgy tranlations by Ben and Google translate -- probably could do better
 label.backupfiles_confirm_delete = Confirmar borrar
 label.backupfiles_confirm_delete_old_files = ¿Borrar los siguientes archivos?
 label.backupfiles_confirm_save_file = Confirmar guardar archivo
 label.backupfiles_confirm_save_file_backupfiles_roll_wrong = Posiblemente algo está mal con los archivos de copia de seguridad. ¿Guardar el nuevo archivo?
+label.backups = Backups
+label.backup_files = Backup Files
+label.enable_backupfiles = Enable use of backup files
+label.suffix_format = Suffix format
+
diff --git a/src/jalview/gui/JalviewBooleanRadioButtons.java b/src/jalview/gui/JalviewBooleanRadioButtons.java
new file mode 100644 (file)
index 0000000..1f0c35a
--- /dev/null
@@ -0,0 +1,107 @@
+package jalview.gui;
+
+import java.awt.Font;
+import java.awt.event.ActionListener;
+
+import javax.swing.AbstractButton;
+import javax.swing.ButtonGroup;
+import javax.swing.JRadioButton;
+
+public class JalviewBooleanRadioButtons extends AbstractButton
+{
+  private static final Font LABEL_FONT = JvSwingUtils.getLabelFont();
+
+  private ButtonGroup buttonGroup = new ButtonGroup();
+
+  private JRadioButton buttonTrue = new JRadioButton();
+
+  private JRadioButton buttonFalse = new JRadioButton();
+
+  public JalviewBooleanRadioButtons(boolean value, String trueLabel,
+          String falseLabel)
+  {
+    init();
+    this.setLabels(trueLabel, falseLabel);
+  }
+
+  public JalviewBooleanRadioButtons(boolean value)
+  {
+    init();
+    setSelected(value);
+  }
+
+  public JalviewBooleanRadioButtons()
+  {
+    init();
+  }
+
+  protected void init()
+  {
+    buttonTrue.setFont(LABEL_FONT);
+    buttonFalse.setFont(LABEL_FONT);
+    buttonGroup.add(buttonTrue);
+    buttonGroup.add(buttonFalse);
+  }
+
+  public void setLabels(String trueLabel, String falseLabel)
+  {
+    buttonTrue.setText(trueLabel);
+    buttonFalse.setText(falseLabel);
+  }
+
+  @Override
+  public void setSelected(boolean b)
+  {
+    buttonFalse.setSelected(!b);
+    // this should probably happen automatically, no harm in forcing the issue!
+    // setting them this way round so the last setSelected is on buttonTrue
+    buttonTrue.setSelected(b);
+  }
+
+  @Override
+  public boolean isSelected()
+  {
+    // unambiguous selection
+    return buttonTrue.isSelected() && !buttonFalse.isSelected();
+  }
+
+  @Override
+  public void setEnabled(boolean b)
+  {
+    buttonTrue.setEnabled(b);
+    buttonFalse.setEnabled(b);
+  }
+
+  @Override
+  public boolean isEnabled()
+  {
+    return buttonTrue.isEnabled() && buttonFalse.isEnabled();
+  }
+
+  public JRadioButton getTrueButton()
+  {
+    return buttonTrue;
+  }
+
+  public JRadioButton getFalseButton()
+  {
+    return buttonFalse;
+  }
+  
+  @Override
+  public void addActionListener(ActionListener l)
+  {
+    buttonTrue.addActionListener(l);
+    buttonFalse.addActionListener(l);
+  }
+
+  public void addTrueActionListener(ActionListener l)
+  {
+    buttonTrue.addActionListener(l);
+  }
+
+  public void addFalseActionListener(ActionListener l)
+  {
+    buttonFalse.addActionListener(l);
+  }
+}
index 7d02fac..8e02c2c 100755 (executable)
@@ -24,6 +24,7 @@ import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.bin.Cache;
 import jalview.gui.Help.HelpId;
 import jalview.gui.StructureViewer.ViewerType;
+import jalview.io.BackupFiles;
 import jalview.io.FileFormatI;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
@@ -794,6 +795,25 @@ public class Preferences extends GPreferences
             Boolean.toString(padGaps.isSelected()));
 
     wsPrefs.updateAndRefreshWsMenuConfig(false);
+
+    /*
+     * Save Backups settings
+     */
+    Cache.applicationProperties.setProperty(BackupFiles.CONFIRM_DELETE_OLD,
+            Boolean.toString(backupfilesConfirmDelete.isSelected()));
+    Cache.applicationProperties.setProperty(BackupFiles.ENABLED,
+            Boolean.toString(enableBackupFiles.isSelected()));
+    Cache.applicationProperties.setProperty(BackupFiles.NO_MAX,
+            Boolean.toString(backupfilesKeepAll.isSelected()));
+    Cache.applicationProperties.setProperty(BackupFiles.REVERSE_ORDER,
+            Boolean.toString(suffixReverse.isSelected()));
+    Cache.applicationProperties.setProperty(BackupFiles.SUFFIX,
+            suffixTemplate.getText());
+    Cache.applicationProperties.setProperty(BackupFiles.ROLL_MAX,
+            Integer.toString(getSpinnerInt(backupfilesRollMaxSpinner, 4)));
+    Cache.applicationProperties.setProperty(BackupFiles.SUFFIX_DIGITS,
+            Integer.toString(getSpinnerInt(suffixDigitsSpinner, 3)));
+
     Cache.saveProperties();
     Desktop.instance.doConfigureStructurePrefs();
     try
index 568f1c9..bb2af1c 100644 (file)
@@ -49,6 +49,16 @@ public class BackupFilenameFilter implements FilenameFilter
     return ret;
   }
 
+  public static String getBackupFilename(int index, String base,
+          String template, int digits, String extension)
+  {
+    String numString = String.format("%0" + digits + "d", index);
+    String backupSuffix = template.replaceAll(BackupFiles.NUM_PLACEHOLDER,
+            numString);
+    String backupfilename = base + backupSuffix + extension;
+    return backupfilename;
+  }
+
 }
 
 class BackupFilenameParts
@@ -120,17 +130,6 @@ class BackupFilenameParts
     
   }
 
-  public static String getBackupFilename(int index, String base,
-          String template,
-          int digits, String extension)
-  {
-    String numString = String.format("%0" + digits + "d", index);
-    String backupSuffix = template.replaceAll(BackupFiles.NUM_PLACEHOLDER,
-            numString);
-    String backupfilename = base + backupSuffix + extension;
-    return backupfilename;
-  }
-
   public boolean isBackupFile()
   {
     return this.isBackupFile;
index ff23cbf..784d8a1 100644 (file)
@@ -16,6 +16,7 @@ import java.util.TreeMap;
  * User configurable options are:
  * BACKUPFILES_ENABLED - boolean flag as to whether to use this mechanism or act as before, including overwriting files as saved.
  * BACKUPFILES_SUFFIX - a template to insert just before the file extension.  Use '%n' to be replaced by a 0-led SUFFIX_DIGITS long integer.
+ * BACKUPFILES_NO_MAX - flag to turn off setting a maximum number of backup files to keep.
  * BACKUPFILES_ROLL_MAX - the maximum number of backupfiles to keep for any one alignment or project file.
  * BACKUPFILES_SUFFIX_DIGITS - the number of digits to insert replace %n with (e.g. BACKUPFILES_SUFFIX_DIGITS = 3 would make "001", "002", etc)
  * BACKUPFILES_REVERSE_ORDER - if true then "logfile" style numbering and file rolling will occur. If false then ever-increasing version numbering will occur, but old files will still be deleted if there are more than ROLL_MAX backup files. 
@@ -32,6 +33,8 @@ public class BackupFiles
 
   public static String SUFFIX = NS + "_SUFFIX";
 
+  public static String NO_MAX = NS + "_NO_MAX";
+
   public static String ROLL_MAX = NS + "_ROLL_MAX";
 
   public static String SUFFIX_DIGITS = NS + "_SUFFIX_DIGITS";
@@ -60,6 +63,9 @@ public class BackupFiles
   // defaultSuffix - default template to use to append to basename of file
   private String suffix;
 
+  // noMax - flag to turn off a maximum number of files
+  private boolean noMax;
+
   // defaultMax - default max number of backup files
   private int max;
 
@@ -81,19 +87,22 @@ public class BackupFiles
     this(new File(filename));
   }
 
-  // first time defaults for SUFFIX, ROLL_MAX, SUFFIX_DIGITS and REVERSE_ORDER
+  // first time defaults for SUFFIX, NO_MAX, ROLL_MAX, SUFFIX_DIGITS and
+  // REVERSE_ORDER
   public BackupFiles(File file)
   {
-    this(file, "-v" + NUM_PLACEHOLDER, 4, 3, false);
+    this(file, "-v" + NUM_PLACEHOLDER, false, 4, 3, false);
   }
 
   public BackupFiles(File file,
-          String defaultSuffix, int defaultMax, int defaultDigits,
+          String defaultSuffix, boolean defaultNoMax, int defaultMax,
+          int defaultDigits,
           boolean defaultReverseOrder)
   {
     classInit();
     this.file = file;
     this.suffix = Cache.getDefault(SUFFIX, defaultSuffix);
+    this.noMax = Cache.getDefault(NO_MAX, defaultNoMax);
     this.max = Cache.getDefault(ROLL_MAX, defaultMax);
     this.digits = Cache.getDefault(SUFFIX_DIGITS, defaultDigits);
     this.reverseOrder = Cache.getDefault(REVERSE_ORDER,
@@ -182,6 +191,11 @@ public class BackupFiles
     return path;
   }
 
+  public static String getNumPlaceHolder()
+  {
+    return NUM_PLACEHOLDER;
+  }
+
   public boolean setWriteSuccess(boolean flag)
   {
     boolean old = this.tempFileWriteSuccess;
@@ -205,7 +219,7 @@ public class BackupFiles
   {
 
     // file doesn't yet exist or backups are not enabled
-    if ((!file.exists()) || (!enabled) || (max < -1))
+    if ((!file.exists()) || (!enabled) || (max < 0))
     {
       // nothing to do
       return true;
@@ -275,24 +289,28 @@ public class BackupFiles
         // backup style numbering
 
         File lastfile = null;
-        int tempMax = max;
-        // max == -1 means no limits
+        int tempMax = noMax ? -1 : max;
+        // noMax == true means no limits
         // look for first "gap" in backupFiles
-        // if tempMax is -1 at this stage just keep going until there's a gap...
+        // if tempMax is -1 at this stage just keep going until there's a gap,
+        // then hopefully tempMax gets set to the right index (a positive
+        // integer so the loop breaks)...
         // why do I feel a little uneasy about this loop?..
-        for (int i = 1; tempMax < 0 || (max >= 0 && i <= max); i++)
+        for (int i = 1; tempMax < 0 || i <= max; i++)
         {
-          if (!bfTreeMap.containsKey(i)) // first non-existent backupfile
+          if (!bfTreeMap.containsKey(i)) // first index without existent
+                                         // backupfile
           {
             tempMax = i;
           }
         }
 
-        for (int m = 0; m < tempMax; m++)
+        // for (int m = 0; m < tempMax; m++)
+        for (int n = tempMax; n > 0; n--)
         {
-          int n = tempMax - m;
+          // int n = tempMax - m;
           String backupfilename = dir + File.separatorChar
-                  + BackupFilenameParts.getBackupFilename(n, basename,
+                  + BackupFilenameFilter.getBackupFilename(n, basename,
                           suffix, digits, extension);
           File backupfile_n = new File(backupfilename);
 
@@ -302,7 +320,8 @@ public class BackupFiles
             continue;
           }
 
-          if (m == 0 && backupfile_n.exists())
+          // if (m == 0 && backupfile_n.exists())
+          if ((!noMax) && n == tempMax && backupfile_n.exists())
           {
             // move the largest (max) rolled file to a temp file and add to the delete list
             try
@@ -343,8 +362,8 @@ public class BackupFiles
 
         bfTreeMap.values().toArray(backupFiles);
 
-        // max value of -1 means keep all backup files
-        if (bfTreeMap.size() >= max && max != -1)
+        // noMax == true means keep all backup files
+        if ((!noMax) && bfTreeMap.size() >= max)
         {
           // need to delete some files to keep number of backups to designated
           // max
@@ -409,7 +428,7 @@ public class BackupFiles
 
     // Let's make the new backup file!! yay, got there at last!
     String latestBackupFilename = dir + File.separatorChar
-            + BackupFilenameParts.getBackupFilename(nextIndexNum, basename,
+            + BackupFilenameFilter.getBackupFilename(nextIndexNum, basename,
                     suffix, digits, extension);
     File latestBackupFile = new File(latestBackupFilename);
     ret = ret && file.renameTo(latestBackupFile);
index 6807382..904f559 100755 (executable)
  */
 package jalview.jbgui;
 
+import jalview.bin.Cache;
 import jalview.fts.core.FTSDataColumnPreferences;
 import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource;
 import jalview.fts.service.pdb.PDBFTSRestClient;
+import jalview.gui.JalviewBooleanRadioButtons;
 import jalview.gui.JvSwingUtils;
 import jalview.gui.StructureViewer.ViewerType;
+import jalview.io.BackupFilenameFilter;
+import jalview.io.BackupFiles;
 import jalview.util.MessageManager;
 
 import java.awt.BorderLayout;
@@ -42,6 +46,7 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.FocusEvent;
 import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 
@@ -57,10 +62,14 @@ import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 import javax.swing.JScrollPane;
+import javax.swing.JSpinner;
 import javax.swing.JTabbedPane;
 import javax.swing.JTable;
+import javax.swing.JTextArea;
 import javax.swing.JTextField;
 import javax.swing.ListSelectionModel;
+import javax.swing.SpinnerModel;
+import javax.swing.SpinnerNumberModel;
 import javax.swing.SwingConstants;
 import javax.swing.border.Border;
 import javax.swing.border.EmptyBorder;
@@ -270,6 +279,42 @@ public class GPreferences extends JPanel
    */
   protected JPanel wsTab = new JPanel();
 
+  /*
+   * Backups tab components
+   * a lot of these are member variables instead of local variables only so that they
+   * can be enabled/disabled easily
+   */
+
+  protected JCheckBox enableBackupFiles = new JCheckBox();
+
+  protected JPanel suffixPanel = new JPanel();
+
+  protected JPanel keepfilesPanel = new JPanel();
+
+  protected JPanel exampleFilesPanel = new JPanel();
+
+  protected JTextField suffixTemplate = new JTextField(null, 8);
+
+  protected JLabel suffixTemplateLabel = new JLabel();
+
+  protected JLabel suffixDigitsLabel = new JLabel();
+
+  protected JSpinner suffixDigitsSpinner = new JSpinner();
+
+  protected JalviewBooleanRadioButtons suffixReverse = new JalviewBooleanRadioButtons();
+
+  protected JalviewBooleanRadioButtons backupfilesKeepAll = new JalviewBooleanRadioButtons();
+
+  protected JSpinner backupfilesRollMaxSpinner = new JSpinner();
+
+  protected JalviewBooleanRadioButtons backupfilesConfirmDelete = new JalviewBooleanRadioButtons();
+
+  protected JLabel exampleLabel = new JLabel();
+
+  protected JScrollPane exampleScrollPane = new JScrollPane();
+
+  protected JTextArea backupfilesExampleLabel = new JTextArea();
+
   /**
    * Creates a new GPreferences object.
    */
@@ -312,6 +357,9 @@ public class GPreferences extends JPanel
     tabbedPane.add(initConnectionsTab(),
             MessageManager.getString("label.connections"));
 
+    tabbedPane.add(initBackupsTab(),
+            MessageManager.getString("label.backups"));
+
     tabbedPane.add(initLinksTab(),
             MessageManager.getString("label.urllinks"));
 
@@ -477,6 +525,12 @@ public class GPreferences extends JPanel
     embbedBioJSON.setText(MessageManager.getString("label.embbed_biojson"));
     embbedBioJSON.setBounds(new Rectangle(228, 200, 250, 23));
 
+
+    TitledBorder backupFilesBorder = new TitledBorder(
+            MessageManager
+                    .getString("label.backup_files"));
+
+
     jPanel11.add(jLabel1);
     jPanel11.add(blcjv);
     jPanel11.add(clustaljv);
@@ -1630,6 +1684,509 @@ public class GPreferences extends JPanel
     return visualTab;
   }
 
+  /**
+   * Initialises the Backups tabbed panel.
+   * 
+   * @return
+   */
+  private JPanel initBackupsTab()
+  {
+    JPanel backupsTab = new JPanel();
+    backupsTab.setBorder(new TitledBorder(MessageManager
+            .getString("label.backup_files")));
+    backupsTab.setLayout(new GridBagLayout());
+
+    enableBackupFiles.setFont(LABEL_FONT);
+    enableBackupFiles.setText(
+            MessageManager.getString("label.enable_backupfiles"));
+    enableBackupFiles
+            .setSelected(Cache.getDefault(BackupFiles.ENABLED, true));
+    enableBackupFiles.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        boolean selected = enableBackupFiles.isSelected();
+        // enable other options only when the first is checked
+        backupsOptionsSetEnabled(selected);
+      }
+    });
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.weightx = 1.0;
+    gbc.weighty = 0.0;
+    gbc.anchor = GridBagConstraints.NORTHWEST;
+
+    // enable checkbox 1 row
+    gbc.gridwidth = 1;
+    gbc.gridy = 0;
+    backupsTab.add(enableBackupFiles, gbc);
+
+    // whole suffix panel next row
+    initBackupsTabSuffixPanel();
+    gbc.gridy = 1;
+    backupsTab.add(suffixPanel, gbc);
+
+    // keep files panel
+    initBackupsTabKeepFilesPanel();
+    gbc.gridy = 2;
+    backupsTab.add(keepfilesPanel, gbc);
+
+    // whole examples panel next row
+    initBackupsTabFilenameExamplesPanel();
+    gbc.gridy = 3;
+    backupsTab.add(exampleFilesPanel, gbc);
+
+    return backupsTab;
+  }
+
+
+  public JPanel initBackupsTabSuffixPanel()
+  {
+    suffixPanel.setBorder(new TitledBorder(
+            MessageManager.getString("label.suffix_format")));
+    suffixPanel.setLayout(new GridBagLayout());
+
+    suffixTemplateLabel
+            .setText(MessageManager.getString("label.suffix_template"));
+    suffixTemplateLabel.setHorizontalAlignment(SwingConstants.LEFT);
+    suffixTemplateLabel.setFont(LABEL_FONT);
+
+    String suffix = Cache.getDefault(BackupFiles.SUFFIX, BackupFiles.getNumPlaceHolder());
+    suffixTemplate.setText(suffix);
+    final String tooltip = JvSwingUtils.wrapTooltip(true, MessageManager
+            .getString("label.suffix_template_tooltip"));
+    suffixTemplate.setToolTipText(tooltip);
+    suffixTemplate.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        updateBackupFilesExampleLabel();
+      }
+
+    });
+    KeyListener kl = new KeyListener()
+    {
+      @Override
+      public void keyReleased(KeyEvent e)
+      {
+        updateBackupFilesExampleLabel();
+      }
+
+      @Override
+      public void keyPressed(KeyEvent e)
+      {
+      }
+
+      // disable use of ':'
+      @Override
+      public void keyTyped(KeyEvent e)
+      {
+        char c = e.getKeyChar();
+        if (c == ':')
+        {
+          // don't process ':'
+          e.consume();
+        }
+      }
+
+    };
+    suffixTemplate.addKeyListener(kl);
+
+    // digits spinner
+    suffixDigitsLabel
+            .setText(MessageManager.getString("label.index_digits"));
+    suffixDigitsLabel.setHorizontalAlignment(SwingConstants.LEFT);
+    suffixDigitsLabel.setFont(LABEL_FONT);
+    int defaultmin = 1;
+    try
+    {
+      defaultmin = Integer.parseInt(
+              MessageManager.getString("default.suffix_index_digits_min"));
+    } catch (Exception e)
+    {
+      defaultmin = 1;
+      System.out.println(
+              "Exception setting suffix digits min from default.suffix_index_digits_min label, setting to "
+                      + defaultmin);
+    }
+    int defaultmax = 6;
+    try {
+      defaultmax = Integer.parseInt(
+              MessageManager.getString("default.suffix_index_digits_max"));
+    } catch (Exception e) {
+      defaultmax = 6;
+      System.out.println(
+              "Exception setting suffix digits max from default.suffix_index_digits_max label, setting to "
+                      + defaultmax);
+    }
+    setIntegerSpinner(suffixDigitsSpinner, defaultmin, defaultmax,
+            Cache.getDefault(BackupFiles.SUFFIX_DIGITS, 3));
+
+    suffixReverse.setSelected(
+            Cache.getDefault(BackupFiles.REVERSE_ORDER, false));
+    suffixReverse.setLabels(
+            MessageManager.getString("label.reverse_roll"),
+            MessageManager.getString("label.increment_index"));
+    suffixReverse.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        updateBackupFilesExampleLabel();
+      }
+    });
+
+    GridBagConstraints sgbc = new GridBagConstraints();
+
+    // first row (template text box)
+    sgbc.anchor = GridBagConstraints.WEST;
+    sgbc.gridx = 0;
+    sgbc.gridy = 0;
+    sgbc.gridwidth = 1;
+    sgbc.gridheight = 1;
+    sgbc.weightx = 1.0;
+    sgbc.weighty = 0.0;
+    sgbc.fill = GridBagConstraints.NONE;
+    suffixPanel.add(suffixTemplateLabel,sgbc);
+    
+    sgbc.gridx = 1;
+    sgbc.fill = GridBagConstraints.HORIZONTAL;
+    suffixPanel.add(suffixTemplate, sgbc);
+    
+    // second row (number of digits spinner)
+    sgbc.gridy = 1;
+    
+    sgbc.gridx = 0;
+    sgbc.fill = GridBagConstraints.NONE;
+    suffixPanel.add(suffixDigitsLabel, sgbc);
+
+    sgbc.gridx = 1;
+    sgbc.fill = GridBagConstraints.HORIZONTAL;
+    suffixPanel.add(suffixDigitsSpinner, sgbc);
+
+    // third row (forward order radio selection)
+    sgbc.gridx = 0;
+    sgbc.gridy = 2;
+    sgbc.gridwidth = GridBagConstraints.REMAINDER;
+    sgbc.fill = GridBagConstraints.HORIZONTAL;
+    suffixPanel.add(suffixReverse.getFalseButton(), sgbc);
+
+    // fourth row (reverse order radio selection)
+    sgbc.gridy = 3;
+    suffixPanel.add(suffixReverse.getTrueButton(), sgbc);
+    return suffixPanel;
+  }
+
+  private JPanel initBackupsTabKeepFilesPanel()
+  {
+    keepfilesPanel.setBorder(
+            new TitledBorder(MessageManager.getString("label.keep_files")));
+    keepfilesPanel.setLayout(new GridBagLayout());
+
+    backupfilesKeepAll
+            .setSelected(Cache.getDefault(BackupFiles.NO_MAX, true));
+    backupfilesKeepAll.setLabels(
+            MessageManager.getString("label.keep_all_backup_files"),
+            MessageManager.getString(
+                    "label.keep_only_this_number_of_backup_files"));
+    backupfilesKeepAll.addTrueActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        updateBackupFilesExampleLabel();
+      }
+    });
+    backupfilesKeepAll.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        boolean selected = backupfilesKeepAll.isSelected();
+        keepRollMaxOptionsEnabled(!selected);
+        updateBackupFilesExampleLabel();
+      }
+    });
+
+    setIntegerSpinner(backupfilesRollMaxSpinner, 1, 999,
+            Cache.getDefault(BackupFiles.ROLL_MAX, 3));
+
+    backupfilesConfirmDelete.setSelected(
+            Cache.getDefault(BackupFiles.CONFIRM_DELETE_OLD, true));
+    backupfilesConfirmDelete.setLabels(
+            MessageManager.getString("label.confirm_delete"),
+            MessageManager.getString("label.auto_delete"));
+    // update the enabled section
+    keepRollMaxOptionsEnabled(!backupfilesKeepAll.isSelected());
+
+    GridBagConstraints kgbc = new GridBagConstraints();
+
+    // first row (template text box)
+    kgbc.anchor = GridBagConstraints.WEST;
+    kgbc.gridx = 0;
+    kgbc.gridy = 0;
+    kgbc.gridwidth = GridBagConstraints.REMAINDER;
+    kgbc.gridheight = 1;
+    kgbc.weightx = 1.0;
+    kgbc.weighty = 0.0;
+    kgbc.fill = GridBagConstraints.HORIZONTAL;
+    keepfilesPanel.add(backupfilesKeepAll.getTrueButton(), kgbc);
+
+    // second row
+    kgbc.gridy = 1;
+
+    kgbc.gridx = 0;
+    kgbc.gridwidth = GridBagConstraints.RELATIVE;
+    keepfilesPanel.add(backupfilesKeepAll.getFalseButton(), kgbc);
+
+    kgbc.gridx = 1;
+    // kgbc.fill = GridBagConstraints.HORIZONTAL;
+    keepfilesPanel.add(backupfilesRollMaxSpinner, kgbc);
+
+    // third row (indented)
+    kgbc.gridy = 2;
+    kgbc.insets = new Insets(0, 20, 0, 0);
+
+    kgbc.gridx = 0;
+    kgbc.gridwidth = GridBagConstraints.REMAINDER;
+    kgbc.fill = GridBagConstraints.HORIZONTAL;
+    kgbc.weightx = 1.0;
+    keepfilesPanel.add(backupfilesConfirmDelete.getTrueButton(), kgbc);
+
+    // fourth row (indented)
+    kgbc.gridy = 3;
+    keepfilesPanel.add(backupfilesConfirmDelete.getFalseButton(), kgbc);
+    return keepfilesPanel;
+  }
+
+  private JPanel initBackupsTabFilenameExamplesPanel()
+  {
+    exampleFilesPanel.setBorder(new TitledBorder(
+            MessageManager.getString("label.examples")));
+    exampleFilesPanel.setLayout(new GridBagLayout());
+
+    exampleLabel.setText(
+            MessageManager.getString("label.suffix_example_filenames"));
+    exampleLabel.setFont(LABEL_FONT);
+    exampleLabel.setHorizontalAlignment(SwingConstants.LEFT);
+
+    backupfilesExampleLabel.setEditable(false);
+    backupfilesExampleLabel.setPreferredSize(new Dimension(300, 100));
+    backupfilesExampleLabel.setAlignmentX(LEFT_ALIGNMENT);
+    backupfilesExampleLabel.setAlignmentY(TOP_ALIGNMENT);
+    
+    backupfilesExampleLabel.setFont(LABEL_FONT_ITALIC);
+    backupfilesExampleLabel.setBackground(exampleLabel.getBackground());
+
+    /*
+    exampleScrollPane.setBounds(0, 0, 200, 100);
+    exampleScrollPane.setPreferredSize(new Dimension(800, 800));
+    exampleScrollPane.add(backupfilesExampleLabel);
+    */
+    updateBackupFilesExampleLabel();
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.anchor = GridBagConstraints.WEST;
+
+    gbc.gridy = 0;
+    exampleFilesPanel.add(exampleLabel, gbc);
+    gbc.gridy = 1;
+    exampleFilesPanel.add(backupfilesExampleLabel, gbc);
+    // exampleFilesPanel.add(exampleScrollPane, gbc);
+    return exampleFilesPanel;
+  }
+
+  private void updateBackupFilesExampleLabel()
+  {
+    int exampleindex = 12;
+    String base = "filename";
+    String extension = ".fa";
+
+    String suffix = suffixTemplate.getText();
+    int digits = 3;
+    try {
+      suffixDigitsSpinner.commitEdit();
+      digits = (Integer) suffixDigitsSpinner.getValue();
+      digits = digits < 1 ? 1 : digits;
+    } catch (Exception e)
+    {
+      System.out.println("Failed casting (Integer) suffixTemplateSpinner.getValue()");
+    }
+    boolean reverse = suffixReverse.isSelected();
+    boolean keepAll = backupfilesKeepAll.isSelected();
+    int rollMax = 4;
+    try
+    {
+      backupfilesRollMaxSpinner.commitEdit();
+      rollMax = (Integer) backupfilesRollMaxSpinner.getValue();
+      rollMax = rollMax < 1 ? 1 : rollMax;
+    } catch (Exception e)
+    {
+      System.out.println(
+              "Failed casting (Integer) backupfilesRollMaxSpinner.getValue()");
+    }
+
+    int surround = 3;
+    StringBuilder exampleSB = new StringBuilder();
+    boolean firstLine = true;
+    if (reverse)
+    {
+
+      int min = 1;
+      int max = keepAll ? exampleindex : rollMax;
+      for (int index = min; index <= max; index++)
+      {
+        if (index == min + surround && index <= max - surround)
+        {
+          exampleSB.append("\n...");
+        }
+        else if (index > min + surround && index <= max - surround)
+        {
+          // nothing
+        }
+        else
+        {
+          if (firstLine)
+          {
+            firstLine = false;
+          }
+          else
+          {
+            exampleSB.append("\n");
+          }
+          exampleSB.append(BackupFilenameFilter.getBackupFilename(index,
+                  base, suffix, digits, extension));
+          if (min == max)
+          {
+            // no extra text needed
+          }
+          else if (index == min)
+          {
+            exampleSB.append(" (most recent)");
+          }
+          else if (index == max)
+          {
+            exampleSB.append(" (oldest)");
+          }
+        }
+      }
+    }
+    else
+    {
+
+      int min = (keepAll || exampleindex - rollMax < 0) ? 1
+              : exampleindex - rollMax + 1;
+      int max = exampleindex;
+
+      for (int index = min; index <= max; index++)
+      {
+
+        if (index == min + surround && index <= max - surround)
+        {
+          exampleSB.append("\n...");
+        }
+        else if (index > min + surround && index <= max - surround)
+        {
+          // nothing
+        }
+        else
+        {
+          if (firstLine)
+          {
+            firstLine = false;
+          }
+          else
+          {
+            exampleSB.append("\n");
+          }
+          exampleSB.append(BackupFilenameFilter.getBackupFilename(index,
+                  base, suffix, digits, extension));
+          if (min == max)
+          {
+            // no extra text needed
+          }
+          else if (index == min)
+          {
+            exampleSB.append(" (oldest)");
+          }
+          else if (index == max)
+          {
+            exampleSB.append(" (most recent)");
+          }
+        }
+      }
+
+    }
+
+    backupfilesExampleLabel.setText(exampleSB.toString());
+  }
+
+  protected void setIntegerSpinner(JSpinner s, int min,
+          int max, int def)
+  {
+    // integer spinner for number of digits
+    if (def > max)
+    {
+      max = def;
+    }
+    SpinnerModel sModel = new SpinnerNumberModel(def, min, max, 1);
+    s.setModel(sModel);
+
+    s.addChangeListener(new ChangeListener()
+    {
+      @Override
+      public void stateChanged(ChangeEvent e)
+      {
+        updateBackupFilesExampleLabel();
+      }
+
+    });
+
+  }
+
+  public static int getSpinnerInt(JSpinner s, int def)
+  {
+    int i = def;
+    try
+    {
+      s.commitEdit();
+      i = (Integer) s.getValue();
+    } catch (Exception e)
+    {
+      System.out.println("Failed casting (Integer) JSpinner s.getValue()");
+    }
+    return i;
+  }
+
+  private void keepRollMaxOptionsEnabled(boolean enabled)
+  {
+    backupfilesRollMaxSpinner.setEnabled(enabled);
+    backupfilesConfirmDelete.setEnabled(enabled);
+
+  }
+  private void backupsOptionsSetEnabled(boolean enabled)
+  {
+    suffixPanel.setEnabled(enabled);
+    keepfilesPanel.setEnabled(enabled);
+    exampleFilesPanel.setEnabled(enabled);
+
+    suffixTemplate.setEnabled(enabled);
+    suffixTemplateLabel.setEnabled(enabled);
+    suffixDigitsLabel.setEnabled(enabled);
+    suffixDigitsSpinner.setEnabled(enabled);
+    suffixReverse.setEnabled(enabled);
+    backupfilesKeepAll.setEnabled(enabled);
+    backupfilesRollMaxSpinner.setEnabled(enabled);
+    backupfilesConfirmDelete.setEnabled(enabled);
+    exampleLabel.setEnabled(enabled);
+    exampleScrollPane.setEnabled(enabled);
+    backupfilesExampleLabel.setEnabled(enabled);
+
+    keepRollMaxOptionsEnabled(enabled && !backupfilesKeepAll.isSelected());
+  }
+
   protected void autoIdWidth_actionPerformed()
   {
     // TODO Auto-generated method stub