/**
 * 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 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.FileInputStream;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.freeshell.zs.common.Debug;
import org.freeshell.zs.common.FileIO;
import org.freeshell.zs.common.SimpleProperties;
import org.freeshell.zs.common.StringManipulator;
import org.freeshell.zs.common.SwingManipulator;
import org.freeshell.zs.common.TerminatingException;


/**
 * "Load Profile" form.
 */
class ProfileLoader
        extends JFrame
{
    /** refresh interval in milliseconds */
    private static final long REFRESH_INTERVAL_MILLISECONDS = 200L;

    /** empty password for decrypting */
    private static final char[] EMPTY_PASSWORD = new char[0];

    /** parent GmailAssistant object */
    private final GmailAssistant parent;

    /** selected profile files to be loaded */
    private final Deque<File> files = new ArrayDeque<File>();

    /** user has responded to the prompt by clicking a button */
    private volatile boolean promptResponded;

    /** profile password to be used for decrypting */
    private volatile char[] promptPassword;

    /** file chooser for selecting profile files */
    private JFileChooser fileChooser = null;


    /**
    * Constructor.
    *
    * @param parent
    *      parent GmailAssistant object
    */
    ProfileLoader(
            final GmailAssistant parent)
    {
        /*********************
        * INITIALIZE FIELDS *
        *********************/

        this.parent = parent;

        /******************************
        * INITIALIZE FORM COMPONENTS *
        ******************************/

        initComponents();

        /***************************
        * CONFIGURE FORM SETTINGS *
        ***************************/

        setTitle(String.format("Load Profiles - %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));
        }

        /* field: "Profile Password" */
        passwordField.setText("");

        /* image: "lock" */
        lockImage.setToolTipText(String.format("%s encrypts your profile using AES-128 encryption", parent.name));

        /* button: "OK" */
        okButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                okButton.setEnabled(false);
                cancelButton.setEnabled(false);
                promptPassword = SwingManipulator.getPasswordJPasswordField(passwordField);
                promptResponded = true;
            }
        });

        /* button: "Cancel" */
        cancelButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                cancelButton.setEnabled(false);
                okButton.setEnabled(false);
                promptPassword = null;
                promptResponded = true;
            }
        });

        /* add standard editing popup menu to text fields */
        SwingManipulator.addStandardEditingPopupMenu(new JTextField[]
        {
            passwordField,
        });

        /* 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();
            }
        });

        /* center form on the parent form */
        setLocationRelativeTo(parent);

        /**********************************
        * INITIATE PROFILE LOADER THREAD *
        **********************************/

        new Thread(new Runnable()
        {
            public void run()
            {
                NextProfile:
                while (true)
                {
                    final File f;

                    synchronized (files)
                    {
                        f = files.pollFirst();
                    }

                    if (f == null)
                    {
                        Debug.sleep(REFRESH_INTERVAL_MILLISECONDS);
                        continue NextProfile;
                    }

                    final int firstByteMarker;
                    byte[] salt = null;
                    byte[] ciphertextBytes = null;
                    final int lastByteMarker;
                    char[] password = null;
                    byte[] cleartextBytes = null;

                    try
                    {
                        /****************
                        * DISPLAY FORM *
                        ****************/

                        SwingUtilities.invokeLater(new Runnable()
                        {
                            public void run()
                            {
                                loadFilename.setText(f.getPath());
                                loadError.setText(" ");
                                passwordField.setEnabled(false);
                                okButton.setEnabled(false);
                                cancelButton.setEnabled(false);
                                setVisible(true);
                                setExtendedState(JFrame.NORMAL);
                                toFront();
                            }
                        });

                        /**********************
                        * READ FILE CONTENTS *
                        **********************/

                        SwingManipulator.updateLabel(loadError, "<html><font color='blue'>Reading file contents...</font></html>");

                        try
                        {
                            final FileInputStream fis = new FileInputStream(f);
                            final int length = (int) f.length();

                            /* read first byte marker */
                            firstByteMarker = fis.read();

                            if (firstByteMarker == -1)
                            {
                                throw new IOException("Encountered premature EOF.");
                            }

                            /* read salt */
                            final int saltLength = parent.properties.getInt("encryption.salt.length");
                            salt = new byte[saltLength];
                            FileIO.blockingRead(fis, salt, 0, saltLength);

                            /* read ciphertext */
                            final int ciphertextLength = length - 2 - saltLength;
                            ciphertextBytes = new byte[ciphertextLength];
                            FileIO.blockingRead(fis, ciphertextBytes, 0, ciphertextLength);

                            /* read first byte marker */
                            lastByteMarker = fis.read();

                            if (lastByteMarker == -1)
                            {
                                throw new IOException("Encountered premature EOF.");
                            }

                            try
                            {
                                fis.close();
                            }
                            catch (Exception e)
                            {
                                /* ignore */
                            }
                        }
                        catch (Exception e)
                        {
                            throw new TerminatingException(String.format(
                                    "Failed to read file contents of profile \"%s\" (%s).\nPlease check that the file exists and can be read.",
                                    f.getPath(), e.toString()));
                        }

                        /******************
                        * CHECK VALIDITY *
                        ******************/

                        SwingManipulator.updateLabel(loadError, "<html><font color='blue'>Checking validity...</font></html>");
                        final int withPasswordByteMarker = parent.properties.getInt("profile.with.password.byte.marker");
                        final int withoutPasswordByteMarker = parent.properties.getInt("profile.without.password.byte.marker");

                        if ((firstByteMarker != lastByteMarker) ||
                                ((firstByteMarker != withPasswordByteMarker) &&
                                (firstByteMarker != withoutPasswordByteMarker)))
                        {
                            throw new TerminatingException(String.format("Malformed profile \"%s\".", f.getPath()));
                        }

                        /******************************************
                        * PROMPT USER FOR PASSWORD, IF NECESSARY *
                        ******************************************/

                        if (firstByteMarker == withPasswordByteMarker)
                        {
                            promptResponded = false;

                            SwingUtilities.invokeLater(new Runnable()
                            {
                                public void run()
                                {
                                    passwordField.setEnabled(true);
                                    okButton.setEnabled(true);
                                    cancelButton.setEnabled(true);
                                    setVisible(true);
                                    setExtendedState(JFrame.NORMAL);
                                    toFront();
                                    passwordField.selectAll();
                                    passwordField.requestFocus();
                                }
                            });

                            /* wait for user to respond */
                            SwingManipulator.updateLabel(loadError, "<html><font color='blue'>Waiting for profile password...</font></html>");

                            while (!promptResponded)
                            {
                                Debug.sleep(REFRESH_INTERVAL_MILLISECONDS);
                            }

                            password = promptPassword;

                            if (password == null)
                            {
                                continue NextProfile;
                            }
                        }
                        else
                        {
                            password = EMPTY_PASSWORD;
                        }

                        /*******************
                        * DECRYPT PROFILE *
                        *******************/

                        SwingManipulator.updateLabel(loadError, "<html><font color='blue'>Decrypting profile...</font></html>");
                        final int iterations = parent.properties.getInt("encryption.salt.iterations");

                        try
                        {
                            cleartextBytes = Encryptor.decrypt(salt, iterations, String.valueOf(password), ciphertextBytes);
                        }
                        catch (Exception e)
                        {
                            synchronized (files)
                            {
                                files.addFirst(f);
                            }

                            throw new TerminatingException(String.format(
                                    "Failed to decrypt profile \"%s\".\nPlease check that the provided password is correct.",
                                    f.getPath()));
                        }

                        final String charset = parent.properties.getString("profile.charset");
                        String[] cleartext = new String(cleartextBytes, charset).split("[\n\r\u0085\u2028\u2028]++");

                        /***************************************
                        * LOAD PROGRAM AND ACCOUNT PROPERTIES *
                        ***************************************/

                        SwingManipulator.updateLabel(loadError, "<html><font color='blue'>Parsing profile...</font></html>");

                        /* convert profile from an older program version, if necessary */
                        final String version = parent.properties.getString("version");
                        String profileVersion = version; /* assume current version first */

                        for (String s : cleartext)
                        {
                            if (s.startsWith("<") && s.endsWith(">"))
                            {
                                profileVersion = s.substring(1, s.length() - 1);
                            }
                        }

                        if (!profileVersion.equals(version))
                        {
                            updateProfile(cleartext, profileVersion);
                        }

                        /* parse each line in the profile file */
                        final List<SimpleProperties> accounts = new ArrayList<SimpleProperties>();
                        SimpleProperties current = null;
                        final List<String> errors = new ArrayList<String>();

                        for (String s : cleartext)
                        {
                            if (s.isEmpty() || s.startsWith("#"))
                            {
                                /* ignore empty lines and comments */
                            }
                            else if (s.startsWith("<") && s.endsWith(">"))
                            {
                                /* ignore program version */
                            }
                            else if (s.startsWith("[") && s.endsWith("]"))
                            {
                                /* start recording properties for a new account */
                                final String username = s.substring(1, s.length() - 1);
                                current = new SimpleProperties(parent.defaultAccountProperties);
                                current.setString("username", username);
                                accounts.add(current);
                            }
                            else if (s.contains(":"))
                            {
                                /* process "key:value" pair */
                                final String[] kv = StringManipulator.parseKeyValueString(s);

                                if (current == null)
                                {
                                    /* program property */
                                    if (parent.savedProgramProperties.get(kv[0]) == null)
                                    {
                                        if (parent.properties.get(kv[0]) == null)
                                        {
                                            errors.add(String.format("\"%s\" is not a valid program property.", kv[0]));
                                        }
                                        else
                                        {
                                            errors.add(String.format("\"%s\" is not a valid saved program property.", kv[0]));
                                        }
                                    }
                                    else
                                    {
                                        parent.properties.set(kv[0], kv[1]);
                                    }
                                }
                                else
                                {
                                    /* account property */
                                    if ("password".equals(kv[0]))
                                    {
                                        current.set(kv[0], kv[1]);
                                    }
                                    else
                                    {
                                        if (parent.savedAccountProperties.get(kv[0]) == null)
                                        {
                                            if (current.get(kv[0]) == null)
                                            {
                                                errors.add(String.format("\"%s\" is not a valid account property.", kv[0]));
                                            }
                                            else
                                            {
                                                errors.add(String.format("\"%s\" is not a valid saved account property.", kv[0]));
                                            }
                                        }
                                        else
                                        {
                                            current.set(kv[0], kv[1]);
                                        }
                                    }
                                }
                            }
                        }

                        SwingManipulator.updateLabel(loadError, "<html><font color='blue'>Profile loaded</font></html>");

                        /* report any errors */
                        if (!errors.isEmpty())
                        {
                            SwingManipulator.updateLabel(loadError, "<html><font color='blue'>Profile loaded (errors encountered)</font></html>");

                            final StringBuilder sb = new StringBuilder();

                            for (String s : errors)
                            {
                                sb.append("\n\n");
                                sb.append(s);
                            }

                            SwingManipulator.showWarningDialog(
                                    ProfileLoader.this,
                                    String.format("Load Profiles - %s", parent.name),
                                    String.format("The following %s were encountered when loading profile \"%s\" :%s",
                                    (errors.size() == 1) ? "error" : "errors", f.getPath(), sb.toString()));
                        }

                        /* add new accounts loaded from the file */
                        for (SimpleProperties p : accounts)
                        {
                            parent.addNewAccount(p);
                        }

                        SwingUtilities.invokeLater(new Runnable()
                        {
                            public void run()
                            {
                                /* repopulate "Options" form */
                                if (parent.options != null)
                                {
                                    parent.options.repopulateForm();
                                }

                                /* refresh program modes */
                                parent.refreshAlwaysOnTopMode();
                                parent.refreshProxyMode();
                            }
                        });
                    }
                    catch (TerminatingException e)
                    {
                        SwingManipulator.showErrorDialog(
                                ProfileLoader.this,
                                String.format("Load Profiles - %s", parent.name),
                                String.format("%s\nNote that profiles created by %s 1.1 or earlier are not compatible with this version of the program.",
                                e.getMessage(), parent.name));
                    }
                    catch (Exception e)
                    {
                        SwingManipulator.showErrorDialog(
                                ProfileLoader.this,
                                String.format("Load Profiles - %s", parent.name),
                                String.format("Failed to load profile \"%s\" because of an unexpected error:\n%s" +
                                "\nNote that profiles created by %s 1.1 or earlier are not compatible with this version of the program." +
                                "\nPlease file a bug report to help improve %s.\n\n%s\n\n%s",
                                f.getPath(), e.toString(), parent.name, parent.name, Debug.getSystemInformationString(), Debug.getStackTraceString(e)));
                    }
                    finally
                    {
                        if (password != null)
                        {
                            Arrays.fill(password, '\0');
                        }

                        for (byte[] b : new byte[][] {salt, ciphertextBytes, cleartextBytes})
                        {
                            if (b != null)
                            {
                                Arrays.fill(b, (byte) 0x00);
                            }
                        }

                        SwingManipulator.setVisibleWindow(ProfileLoader.this, false);
                    }
                }
            }
        }).start();
    }


    /**
    * Present a file chooser for the user to select the profile files for loading.
    * This method must run on the EDT.
    */
    void showForm()
    {
        if (fileChooser == null)
        {
            try
            {
                fileChooser = new JFileChooser(new File(".").getCanonicalFile());
            }
            catch (Exception e)
            {
                fileChooser = new JFileChooser();
            }

            fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
            fileChooser.setMultiSelectionEnabled(true);

            final String ext = parent.properties.getString("profile.default.extension");

            fileChooser.addChoosableFileFilter(new FileNameExtensionFilter(
                    String.format("%s Profile (*.%s)", parent.name, ext),
                    ext));
        }

        final int val = fileChooser.showOpenDialog(parent);

        if (val == JFileChooser.APPROVE_OPTION)
        {
            synchronized (files)
            {
                for (File f : fileChooser.getSelectedFiles())
                {
                    files.offerLast(f);
                }
            }
        }
    }


    /**
    * Load the specified profile files.
    * This method must run on the EDT.
    *
    * @param files
    *      profile files to be loaded
    */
    void loadProfiles(
            final List<File> fs)
    {
        synchronized (files)
        {
            for (File f : fs)
            {
                files.offerLast(f);
            }
        }
    }


    /**
    * Update the specified profile data to the current program version.
    *
    * @param profile
    *      profile data to be updated
    * @param version
    *      program version associated with the given profile data
    */
    private void updateProfile(
            final String[] profile,
            final String version)
    {
        /* nothing to do */
    }

    /***************************
    * 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() {

        buttonsPanel = new javax.swing.JPanel();
        okButton = new javax.swing.JButton();
        cancelButton = new javax.swing.JButton();
        loadTitle = new javax.swing.JLabel();
        loadFilename = new javax.swing.JLabel();
        scrollPane = new javax.swing.JScrollPane();
        panel = new javax.swing.JPanel();
        loadPanel = new javax.swing.JPanel();
        passwordLabel = new javax.swing.JLabel();
        passwordField = new javax.swing.JPasswordField();
        lockImage = new javax.swing.JLabel();
        loadError = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);

        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("Load profile from 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 loading of profile");
        buttonsPanel.add(cancelButton);

        loadTitle.setText("Loading profile");
        loadTitle.setVerticalAlignment(javax.swing.SwingConstants.TOP);

        loadFilename.setFont(new java.awt.Font("Tahoma", 1, 11));
        loadFilename.setText("C:\\Path\\To\\File");
        loadFilename.setVerticalAlignment(javax.swing.SwingConstants.TOP);

        scrollPane.setBorder(null);

        panel.setLayout(new javax.swing.BoxLayout(panel, javax.swing.BoxLayout.Y_AXIS));

        loadPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(""));

        passwordLabel.setDisplayedMnemonic('p');
        passwordLabel.setLabelFor(passwordField);
        passwordLabel.setText("Profile password:");
        passwordLabel.setToolTipText("Password to be used for decrypting the profile");

        passwordField.setToolTipText("Password to be used for decrypting the profile");

        lockImage.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        lockImage.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/freeshell/zs/gmailassistant/resources/lock.png"))); // NOI18N

        loadError.setText("<html><font color='red'>load error</font></html>");
        loadError.setVerticalAlignment(javax.swing.SwingConstants.TOP);

        javax.swing.GroupLayout loadPanelLayout = new javax.swing.GroupLayout(loadPanel);
        loadPanel.setLayout(loadPanelLayout);
        loadPanelLayout.setHorizontalGroup(
            loadPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, loadPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(loadPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(loadError, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 267, Short.MAX_VALUE)
                    .addGroup(loadPanelLayout.createSequentialGroup()
                        .addComponent(passwordLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(passwordField, javax.swing.GroupLayout.DEFAULT_SIZE, 160, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(lockImage)))
                .addContainerGap())
        );
        loadPanelLayout.setVerticalGroup(
            loadPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(loadPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(loadPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(loadPanelLayout.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))
                .addGap(18, 18, 18)
                .addComponent(loadError)
                .addContainerGap(13, Short.MAX_VALUE))
        );

        panel.add(loadPanel);

        scrollPane.setViewportView(panel);

        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(scrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 299, Short.MAX_VALUE)
                    .addComponent(buttonsPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 299, Short.MAX_VALUE)
                    .addComponent(loadTitle, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 299, Short.MAX_VALUE)
                    .addComponent(loadFilename, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 299, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(loadTitle)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(loadFilename)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 88, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(buttonsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 38, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JPanel buttonsPanel;
    private javax.swing.JButton cancelButton;
    private javax.swing.JLabel loadError;
    private javax.swing.JLabel loadFilename;
    private javax.swing.JPanel loadPanel;
    private javax.swing.JLabel loadTitle;
    private javax.swing.JLabel lockImage;
    private javax.swing.JButton okButton;
    private javax.swing.JPanel panel;
    private javax.swing.JPasswordField passwordField;
    private javax.swing.JLabel passwordLabel;
    private javax.swing.JScrollPane scrollPane;
    // End of variables declaration//GEN-END:variables
}