/**
* GmailAssistant 2.0 (2008-09-07)
* Copyright 2008 Zach Scrivena
* zachscrivena@gmail.com
* http://gmailassistant.sourceforge.net/
*
* Notifier for multiple Gmail and Google Apps email accounts.
*
* TERMS AND CONDITIONS:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeshell.zs.gmailassistant;
import java.awt.Component;
import org.freeshell.zs.common.Encryptor;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.text.JTextComponent;
import org.freeshell.zs.common.Debug;
import org.freeshell.zs.common.SwingManipulator;
import org.freeshell.zs.common.TerminatingException;
/**
* "Save Profile" form.
*/
class ProfileSaver
extends JFrame
{
/** parent GmailAssistant object */
private final GmailAssistant parent;
/** are the "Options" selection valid? */
private boolean optionsValid = false;
/** is the "Filename" selection valid? */
private boolean filenameValid = false;
/** file chooser for selecting profile file */
private JFileChooser fileChooser = null;
/**
* Constructor.
*
* @param parent
* parent GmailAssistant object
*/
ProfileSaver(
final GmailAssistant parent)
{
/*********************
* INITIALIZE FIELDS *
*********************/
this.parent = parent;
/******************************
* INITIALIZE FORM COMPONENTS *
******************************/
initComponents();
/***************************
* CONFIGURE FORM SETTINGS *
***************************/
setTitle(String.format("Save Profile - %s", parent.name));
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
cancelButton.doClick();
}
});
/* inherit "always on top" behavior of parent */
try
{
setAlwaysOnTop(parent.isAlwaysOnTop());
}
catch (Exception e)
{
/* ignore */
}
/* inherit program icon of parent */
final List<Image> icons = parent.getIconImages();
if (!icons.isEmpty())
{
setIconImage(icons.get(0));
}
/* image: "lock" */
lockImage.setToolTipText(String.format("%s encrypts your profile using AES-128 encryption", parent.name));
/* checkbox: "Save account passwords" */
savePasswordsBox.setSelected(parent.properties.getBoolean("default.save.account.passwords"));
/* monitor editing of fields: "Profile password", "Filename" */
for (final JTextComponent c : new JTextComponent[] {passwordField, filenameField})
{
c.getDocument().addDocumentListener(new DocumentListenerAdapter()
{
@Override
public void insertUpdate(DocumentEvent e)
{
checkForm(c);
}
@Override
public void removeUpdate(DocumentEvent e)
{
checkForm(c);
}
@Override
public void changedUpdate(DocumentEvent e)
{
checkForm(c);
}
});
}
/* button: "Browse..." */
browseButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
browseButton.setEnabled(false);
final String ext = parent.properties.getString("profile.default.extension");
if (fileChooser == null)
{
try
{
fileChooser = new JFileChooser(new File(".").getCanonicalFile());
}
catch (Exception ex)
{
fileChooser = new JFileChooser();
}
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileChooser.setMultiSelectionEnabled(false);
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter(
String.format("%s Profile (*.%s)", parent.name, ext),
ext));
}
final int val = fileChooser.showSaveDialog(ProfileSaver.this);
if (val == JFileChooser.APPROVE_OPTION)
{
File f = fileChooser.getSelectedFile();
if (!f.getName().contains("."))
{
f = new File(f.getParentFile(), String.format("%s.%s", f.getName(), ext));
}
filenameField.setText(f.getPath());
}
browseButton.setEnabled(true);
}
});
/* button: "OK" */
okButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
okButton.setEnabled(false);
cancelButton.setEnabled(false);
final char[] password = SwingManipulator.getPasswordJPasswordField(passwordField);
final boolean saveAccountPasswords = savePasswordsBox.isSelected();
final File profileFile = new File(SwingManipulator.getTextJTextField(filenameField));
new Thread(new Runnable()
{
public void run()
{
saveProfile(password, saveAccountPasswords, profileFile);
}
}).start();
}
});
/* button: "Cancel" */
cancelButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
cancelButton.setEnabled(false);
okButton.setEnabled(false);
setVisible(false);
}
});
/* add standard editing popup menu to text fields */
SwingManipulator.addStandardEditingPopupMenu(new JTextField[]
{
passwordField,
filenameField
});
/* key binding: ENTER key */
scrollPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "ENTER_OK_BUTTON");
scrollPane.getActionMap().put("ENTER_OK_BUTTON", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
okButton.doClick();
}
});
/* key binding: ESCAPE key */
scrollPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "ESCAPE_CANCEL_BUTTON");
scrollPane.getActionMap().put("ESCAPE_CANCEL_BUTTON", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
cancelButton.doClick();
}
});
/* check form for errors */
checkForm(null);
/* center form on the parent form */
setLocationRelativeTo(parent);
/****************************
* GENERATE UNUSED FILENAME *
****************************/
File dir;
try
{
dir = new File(".").getCanonicalFile();
}
catch (Exception e)
{
dir = new File(".");
}
final String filename = parent.properties.getString("profile.default.filename");
final String ext = parent.properties.getString("profile.default.extension");
File f = null;
for (int i = 0; i < Integer.MAX_VALUE; i++)
{
if (i == 0)
{
f = new File(dir, String.format("%s.%s", filename, ext));
}
else
{
f = new File(dir, String.format("%s.%d.%s", filename, i, ext));
}
if (!f.exists())
{
break;
}
}
if (f.exists())
{
f = new File(dir, String.format("%s.%s", filename, ext));
}
filenameField.setText(f.getPath());
}
/**
* Check user selections on the form for errors.
* This method must run on the EDT.
*
* @param c
* component that has triggered the check
*/
private void checkForm(
final Component c)
{
boolean checkOptions = false;
boolean checkFilename = false;
if (c == passwordField)
{
checkOptions = true;
}
else if (c == filenameField)
{
checkFilename = true;
}
else if (c == null)
{
checkOptions = true;
checkFilename = true;
}
if (checkOptions)
{
String warning = null;
final char[] password = SwingManipulator.getPasswordJPasswordField(passwordField);
final int passwordLength = password.length;
Arrays.fill(password, '\0');
final int min = parent.properties.getInt("recommended.minimum.password.length");
if (passwordLength == 0)
{
warning = "A password is strongly recommended";
}
else if (passwordLength < min)
{
warning = String.format("A password of at least %d characters is recommended", min);
}
if (warning == null)
{
optionsValid = true;
optionsError.setText(" ");
}
else
{
optionsValid = true;
optionsError.setText(String.format("<html><font color='red'>%s</font></html>", warning));
}
}
if (checkFilename)
{
String error = null;
if (SwingManipulator.getTextJTextField(filenameField).trim().isEmpty())
{
error = "A filename must be specified";
}
if (error == null)
{
filenameValid = true;
filenameError.setText(" ");
}
else
{
filenameValid = false;
filenameError.setText(String.format("<html><font color='red'>%s</font></html>", error));
okButton.setEnabled(false);
filenameField.selectAll();
filenameField.requestFocus();
}
}
if (optionsValid && filenameValid)
{
/* all selections are valid */
okButton.setEnabled(true);
}
}
/**
* Present the form for saving a profile.
* This method must run on the EDT.
*/
void showForm()
{
okButton.setEnabled(true);
cancelButton.setEnabled(true);
browseButton.setEnabled(true);
checkForm(null);
setVisible(true);
setExtendedState(JFrame.NORMAL);
toFront();
passwordField.selectAll();
passwordField.requestFocus();
}
/**
* Save profile using the user-specified settings on the form.
* This method should run on a dedicated worker thread, not the EDT.
*
* @param password
* password for encrypting profile
* @param saveAccountPasswords
* should account passwords be saved in the profile?
*/
private void saveProfile(
final char[] password,
final boolean saveAccountPasswords,
final File profileFile)
{
byte[] salt = null;
final List<byte[]> cleartext = new ArrayList<byte[]>();
byte[] cleartextBytes = null;
byte[] ciphertextBytes = null;
try
{
/**************************
* CHECK VALIDITY OF FILE *
**************************/
SwingManipulator.updateLabel(filenameError, "<html><font color='blue'>Checking file...</font></html>");
if (profileFile.isDirectory())
{
throw new TerminatingException(String.format(
"Failed to save profile to \"%s\" because it is a directory and not a file.",
profileFile.getPath()));
}
if (profileFile.exists())
{
/* file already exists; prompt on overwriting */
final int choice = JOptionPane.showConfirmDialog(
this,
String.format("File \"%s\" already exists. Overwrite this file?", profileFile.getPath()),
String.format("Save Profile - %s", parent.name),
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (choice != JOptionPane.YES_OPTION)
{
return;
}
}
/***************************
* SAVE PROGRAM PROPERTIES *
***************************/
SwingManipulator.updateLabel(filenameError, "<html><font color='blue'>Preparing profile...</font></html>");
final String charset = parent.properties.getString("profile.charset");
final byte[] newlineBytes = "\n".getBytes(charset);
final byte[] colonBytes = ":".getBytes(charset);
final byte[] leftAngleBytes = "<".getBytes(charset);
final byte[] rightAngleBytes = ">".getBytes(charset);
final byte[] leftSquareBytes = "[".getBytes(charset);
final byte[] rightSquareBytes = "]".getBytes(charset);
/* "<version>" */
cleartext.add(leftAngleBytes);
cleartext.add(parent.properties.getString("version").getBytes(charset));
cleartext.add(rightAngleBytes);
cleartext.add(newlineBytes);
for (String k : parent.savedProgramProperties.keySet())
{
/* write "key:value" pair */
cleartext.add(k.getBytes(charset));
cleartext.add(colonBytes);
cleartext.add(parent.properties.getAsString(k).getBytes(charset));
cleartext.add(newlineBytes);
}
/***************************
* SAVE ACCOUNT PROPERTIES *
***************************/
parent.saveAccountProperties(
cleartext,
saveAccountPasswords,
charset,
newlineBytes,
colonBytes,
leftAngleBytes,
rightAngleBytes,
leftSquareBytes,
rightSquareBytes);
/* combine list of byte arrays to a single byte array */
int len = 0;
for (byte[] b : cleartext)
{
len += b.length;
}
cleartextBytes = new byte[len];
int pos = 0;
for (byte[] b : cleartext)
{
System.arraycopy(b, 0, cleartextBytes, pos, b.length);
pos += b.length;
}
/*******************
* ENCRYPT PROFILE *
*******************/
SwingManipulator.updateLabel(filenameError, "<html><font color='blue'>Encrypting profile...</font></html>");
salt = new byte[parent.properties.getInt("encryption.salt.length")];
final int iterations = parent.properties.getInt("encryption.salt.iterations");
final int withPasswordByteMarker = parent.properties.getInt("profile.with.password.byte.marker");
final int withoutPasswordByteMarker = parent.properties.getInt("profile.without.password.byte.marker");
ciphertextBytes = Encryptor.encrypt(salt, iterations, String.valueOf(password), cleartextBytes);
/*********************
* WRITE CIPHER TEXT *
*********************/
SwingManipulator.updateLabel(filenameError, "<html><font color='blue'>Writing profile...</font></html>");
try
{
final FileOutputStream fos = new FileOutputStream(profileFile);
fos.write((password.length == 0) ? withoutPasswordByteMarker : withPasswordByteMarker);
fos.write(salt);
fos.write(ciphertextBytes);
fos.write((password.length == 0) ? withoutPasswordByteMarker : withPasswordByteMarker);
fos.flush();
fos.close();
}
catch (Exception e)
{
throw new TerminatingException(String.format(
"Failed to write profile to file \"%s\":\n%s" +
"\nPlease check that the file can be created and written to.",
profileFile.getPath(), e.toString()));
}
SwingManipulator.updateLabel(filenameError, "<html><font color='blue'>Profile saved</font></html>");
SwingManipulator.setVisibleWindow(this, false);
}
catch (TerminatingException e)
{
SwingManipulator.showErrorDialog(
this,
String.format("Save Profile - %s", parent.name),
e.getMessage());
}
catch (Exception e)
{
SwingManipulator.showErrorDialog(
this,
String.format("Save Profile - %s", parent.name),
String.format("Failed to save profile \"%s\" because of an unexpected error:\n%s" +
"\nPlease file a bug report to help improve %s.\n\n%s\n\n%s",
profileFile.getPath(), e.toString(), parent.name, Debug.getSystemInformationString(), Debug.getStackTraceString(e)));
}
finally
{
Arrays.fill(password, '\0');
for (byte[] b : new byte[][] {salt, cleartextBytes, ciphertextBytes})
{
if (b != null)
{
Arrays.fill(b, (byte) 0x00);
}
}
for (byte[] b : cleartext)
{
Arrays.fill(b, (byte) 0x00);
}
SwingManipulator.updateLabel(filenameError, " ");
SwingManipulator.setEnabledButton(okButton, true);
SwingManipulator.setEnabledButton(cancelButton, true);
}
}
/***************************
* NETBEANS-GENERATED CODE *
***************************/
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
scrollPane = new javax.swing.JScrollPane();
panel = new javax.swing.JPanel();
optionsPanel = new javax.swing.JPanel();
lockImage = new javax.swing.JLabel();
passwordLabel = new javax.swing.JLabel();
savePasswordsBox = new javax.swing.JCheckBox();
passwordField = new javax.swing.JPasswordField();
optionsError = new javax.swing.JLabel();
filenamePanel = new javax.swing.JPanel();
browseButton = new javax.swing.JButton();
filenameField = new javax.swing.JTextField();
filenameError = new javax.swing.JLabel();
buttonsPanel = new javax.swing.JPanel();
okButton = new javax.swing.JButton();
cancelButton = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
scrollPane.setBorder(null);
panel.setLayout(new javax.swing.BoxLayout(panel, javax.swing.BoxLayout.Y_AXIS));
optionsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Options"));
lockImage.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
lockImage.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/freeshell/zs/gmailassistant/resources/lock.png"))); // NOI18N
passwordLabel.setDisplayedMnemonic('p');
passwordLabel.setLabelFor(passwordField);
passwordLabel.setText("Profile password:");
passwordLabel.setToolTipText("Password to be used for encrypting the profile");
savePasswordsBox.setMnemonic('s');
savePasswordsBox.setText("Save account passwords in profile");
savePasswordsBox.setToolTipText("Save Gmail account passwords in the profile so they do not need to be entered again when the profile is loaded");
passwordField.setToolTipText("Password to be used for encrypting the profile");
optionsError.setText("<html><font color='red'>options error</font></html>");
optionsError.setVerticalAlignment(javax.swing.SwingConstants.TOP);
javax.swing.GroupLayout optionsPanelLayout = new javax.swing.GroupLayout(optionsPanel);
optionsPanel.setLayout(optionsPanelLayout);
optionsPanelLayout.setHorizontalGroup(
optionsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(optionsPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(optionsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(optionsError, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 271, Short.MAX_VALUE)
.addComponent(savePasswordsBox, javax.swing.GroupLayout.DEFAULT_SIZE, 271, Short.MAX_VALUE)
.addGroup(optionsPanelLayout.createSequentialGroup()
.addComponent(passwordLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(passwordField, javax.swing.GroupLayout.DEFAULT_SIZE, 164, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lockImage)))
.addContainerGap())
);
optionsPanelLayout.setVerticalGroup(
optionsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(optionsPanelLayout.createSequentialGroup()
.addGroup(optionsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(optionsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(passwordLabel)
.addComponent(passwordField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(lockImage))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(savePasswordsBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(optionsError)
.addContainerGap(13, Short.MAX_VALUE))
);
panel.add(optionsPanel);
filenamePanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Filename"));
browseButton.setMnemonic('b');
browseButton.setText("Browse...");
browseButton.setToolTipText("Choose a filename for the profile");
filenameField.setText("C:\\Path\\To\\File");
filenameField.setToolTipText("Filename for the profile");
filenameError.setText("<html><font color='red'>filename error</font></html>");
filenameError.setVerticalAlignment(javax.swing.SwingConstants.TOP);
javax.swing.GroupLayout filenamePanelLayout = new javax.swing.GroupLayout(filenamePanel);
filenamePanel.setLayout(filenamePanelLayout);
filenamePanelLayout.setHorizontalGroup(
filenamePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, filenamePanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(filenamePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(filenameField, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 271, Short.MAX_VALUE)
.addGroup(filenamePanelLayout.createSequentialGroup()
.addComponent(filenameError, javax.swing.GroupLayout.DEFAULT_SIZE, 186, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(browseButton)))
.addContainerGap())
);
filenamePanelLayout.setVerticalGroup(
filenamePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(filenamePanelLayout.createSequentialGroup()
.addComponent(filenameField, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(filenamePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(browseButton)
.addComponent(filenameError))
.addContainerGap(14, Short.MAX_VALUE))
);
panel.add(filenamePanel);
scrollPane.setViewportView(panel);
buttonsPanel.setLayout(new java.awt.GridLayout(1, 2));
okButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/freeshell/zs/gmailassistant/resources/tick.png"))); // NOI18N
okButton.setMnemonic('O');
okButton.setText("OK");
okButton.setToolTipText("Save profile to the specified file");
buttonsPanel.add(okButton);
cancelButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/freeshell/zs/gmailassistant/resources/cross.png"))); // NOI18N
cancelButton.setMnemonic('C');
cancelButton.setText("Cancel");
cancelButton.setToolTipText("Cancel saving of profile");
buttonsPanel.add(cancelButton);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(buttonsPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 303, Short.MAX_VALUE)
.addComponent(scrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 303, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 205, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(buttonsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 37, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);
pack();
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton browseButton;
private javax.swing.JPanel buttonsPanel;
private javax.swing.JButton cancelButton;
private javax.swing.JLabel filenameError;
private javax.swing.JTextField filenameField;
private javax.swing.JPanel filenamePanel;
private javax.swing.JLabel lockImage;
private javax.swing.JButton okButton;
private javax.swing.JLabel optionsError;
private javax.swing.JPanel optionsPanel;
private javax.swing.JPanel panel;
private javax.swing.JPasswordField passwordField;
private javax.swing.JLabel passwordLabel;
private javax.swing.JCheckBox savePasswordsBox;
private javax.swing.JScrollPane scrollPane;
// End of variables declaration//GEN-END:variables
}