/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * Copyright (C) 2011-2016 Brian P. Hinz
 *
 * This 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

package com.tigervnc.vncviewer;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.text.Format;
import java.text.NumberFormat;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.UIManager.*;
import javax.swing.text.*;
import java.util.*;
import java.util.Map.Entry;

import com.tigervnc.rfb.*;

import static java.awt.GridBagConstraints.BOTH;
import static java.awt.GridBagConstraints.CENTER;
import static java.awt.GridBagConstraints.HORIZONTAL;
import static java.awt.GridBagConstraints.LINE_END;
import static java.awt.GridBagConstraints.LINE_START;
import static java.awt.GridBagConstraints.PAGE_START;
import static java.awt.GridBagConstraints.NONE;
import static java.awt.GridBagConstraints.RELATIVE;
import static java.awt.GridBagConstraints.REMAINDER;
import static java.awt.GridBagConstraints.VERTICAL;

class OptionsDialog extends Dialog {

  private class IntegerDocument extends PlainDocument {
    private int limit;

    public IntegerDocument(int max) {
      super();
      limit = max;
    }

    public void insertString(int offset, String str, AttributeSet a)
          throws BadLocationException {
      if (str == null || !str.matches("^[0-9]+$")) return;
      if ((getLength() + str.length()) > limit)
        Toolkit.getDefaultToolkit().beep();
      else
        super.insertString(offset, str, a);
    }
  }

  private class IntegerTextField extends JFormattedTextField {
    public IntegerTextField(int digits) {
      super();
      setDocument(new IntegerDocument(digits));
      Font f = getFont();
      String template = String.format("%0"+digits+"d", 0);
      int w = getFontMetrics(f).stringWidth(template) +
              getMargin().left + getMargin().right +
              getInsets().left + getInsets().right;
      int h = getPreferredSize().height;
      setPreferredSize(new Dimension(w, h));
    }

    @Override
    protected void processFocusEvent(final FocusEvent e) {
      if (e.isTemporary())
        return;
      if (e.getID() == FocusEvent.FOCUS_LOST)
        if (getText() == null || getText().isEmpty())
          setValue(null);
      super.processFocusEvent(e);
    }
  }

  // Constants
  static LogWriter vlog = new LogWriter("OptionsDialog");

  CConn cc;
  @SuppressWarnings({"rawtypes"})
  JComboBox menuKey, compressLevel, qualityLevel, scalingFactor;
  ButtonGroup encodingGroup, colourGroup, sshArgsGroup;
  JRadioButton zrle, hextile, tight, raw, fullColour, mediumColour,
               lowColour, veryLowColour, sshArgsDefault, sshArgsCustom;
  JCheckBox autoSelect, customCompressLevel, noJpeg, viewOnly,
            acceptClipboard, sendClipboard, acceptBell, desktopSize,
            fullScreen, fullScreenAllMonitors, shared, useLocalCursor,
            secVeNCrypt, encNone, encTLS, encX509, secNone, secVnc,
            secPlain, secIdent, sendLocalUsername, sshTunnel, sshUseExt,
            sshUseGateway;
  JButton okButton, cancelButton, caButton, crlButton, cfLoadButton,
          cfSaveAsButton, defSaveButton, defReloadButton, defClearButton,
          sshConfigBrowser, sshKeyFileBrowser, sshClientBrowser;
  JTextField desktopWidth, desktopHeight, x509ca, x509crl, sshUser, sshHost,
             sshPort, sshClient, sshArguments, sshConfig, sshKeyFile;
  JTabbedPane tabPane;

  @SuppressWarnings({"rawtypes","unchecked"})
  public OptionsDialog(CConn cc_) {
    super(true);
    cc = cc_;
    setTitle("VNC Viewer Options");
    setResizable(false);

    getContentPane().setLayout(
      new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS));

    tabPane = new JTabbedPane();
    tabPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);

    encodingGroup = new ButtonGroup();
    colourGroup = new ButtonGroup();
    sshArgsGroup = new ButtonGroup();
    int indent = 0;

    // Compression tab
    JPanel FormatPanel = new JPanel();
    FormatPanel.setLayout(new BoxLayout(FormatPanel,
                                        BoxLayout.PAGE_AXIS));
    FormatPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));

    JPanel autoSelectPane = new JPanel();
    autoSelectPane.setLayout(new BoxLayout(autoSelectPane,
                                           BoxLayout.LINE_AXIS));
    autoSelectPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
    autoSelect = new JCheckBox("Auto Select");
    autoSelectPane.add(autoSelect);
    autoSelectPane.add(Box.createHorizontalGlue());

    JPanel encodingPanel = new JPanel(new GridLayout(4, 1));
    encodingPanel.
      setBorder(BorderFactory.createTitledBorder("Preferred encoding"));
    tight = new GroupedJRadioButton("Tight",
                                    encodingGroup, encodingPanel);
    zrle = new GroupedJRadioButton("ZRLE",
                                    encodingGroup, encodingPanel);
    hextile = new GroupedJRadioButton("Hextile",
                                      encodingGroup, encodingPanel);
    raw = new GroupedJRadioButton("Raw", encodingGroup, encodingPanel);

    JPanel colourPanel = new JPanel(new GridLayout(4, 1));
    colourPanel.setBorder(BorderFactory.createTitledBorder("Color level"));
    fullColour = new GroupedJRadioButton("Full (all available colors)",
                                          colourGroup, colourPanel);
    mediumColour = new GroupedJRadioButton("Medium (256 colors)",
                                            colourGroup, colourPanel);
    lowColour = new GroupedJRadioButton("Low (64 colours)",
                                        colourGroup, colourPanel);
    veryLowColour = new GroupedJRadioButton("Very low(8 colors)",
                                            colourGroup, colourPanel);

    JPanel encodingPane = new JPanel(new GridLayout(1, 2, 5, 0));
    encodingPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
    encodingPane.add(encodingPanel);
    encodingPane.add(colourPanel);

    JPanel tightPanel = new JPanel(new GridBagLayout());
    customCompressLevel = new JCheckBox("Custom Compression Level");
    Object[] compressionLevels = { 1, 2, 3, 4, 5, 6 };
    compressLevel  = new MyJComboBox(compressionLevels);
    ((MyJComboBox)compressLevel).setDocument(new IntegerDocument(1));
    compressLevel.setPrototypeDisplayValue("0.");
    compressLevel.setEditable(true);
    JLabel compressionLabel =
      new JLabel("Level (1=fast, 6=best [4-6 are rarely useful])");
    noJpeg = new JCheckBox("Allow JPEG Compression");
    Object[] qualityLevels = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    qualityLevel  = new MyJComboBox(qualityLevels);
    qualityLevel.setPrototypeDisplayValue("0.");
    JLabel qualityLabel = new JLabel("Quality (0=poor, 9=best)");

    tightPanel.add(customCompressLevel,
                   new GridBagConstraints(0, 0,
                                          REMAINDER, 1,
                                          LIGHT, LIGHT,
                                          LINE_START, NONE,
                                          new Insets(0, 0, 0, 0),
                                          NONE, NONE));
    indent = getButtonLabelInset(customCompressLevel);
    tightPanel.add(compressLevel,
                   new GridBagConstraints(0, 1,
                                          1, 1,
                                          LIGHT, LIGHT,
                                          LINE_START, NONE,
                                          new Insets(0, indent, 0, 0),
                                          NONE, NONE));
    tightPanel.add(compressionLabel,
                   new GridBagConstraints(1, 1,
                                          1, 1,
                                          HEAVY, LIGHT,
                                          LINE_START, HORIZONTAL,
                                          new Insets(0, 5, 0, 0),
                                          NONE, NONE));
    tightPanel.add(noJpeg,
                   new GridBagConstraints(0, 2,
                                          REMAINDER, 1,
                                          LIGHT, LIGHT, 
                                          LINE_START, NONE,
                                          new Insets(5, 0, 0, 0),
                                          NONE, NONE));
    indent = getButtonLabelInset(noJpeg);
    tightPanel.add(qualityLevel,
                   new GridBagConstraints(0, 3,
                                          1, 1,
                                          LIGHT, LIGHT,
                                          LINE_START, NONE,
                                          new Insets(0, indent, 0, 0),
                                          NONE, NONE));
    tightPanel.add(qualityLabel,
                   new GridBagConstraints(1, 3,
                                          1, 1,
                                          HEAVY, NONE,
                                          LINE_START, HORIZONTAL,
                                          new Insets(0, 5, 0, 0),
                                          NONE, NONE));
    tightPanel.add(Box.createRigidArea(new Dimension(5,0)),
                   new GridBagConstraints(0, 4,
                                          REMAINDER, REMAINDER,
                                          HEAVY, HEAVY,
                                          LINE_START, BOTH,
                                          new Insets(0, 0, 0, 0),
                                          NONE, NONE));
    FormatPanel.add(autoSelectPane);
    FormatPanel.add(encodingPane);
    FormatPanel.add(tightPanel);

    // security tab
    JPanel SecPanel = new JPanel();
    SecPanel.setLayout(new BoxLayout(SecPanel,
                                     BoxLayout.PAGE_AXIS));
    SecPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));

    JPanel vencryptPane = new JPanel();
    vencryptPane.setLayout(new BoxLayout(vencryptPane,
                                         BoxLayout.LINE_AXIS));
    vencryptPane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
    secVeNCrypt = new JCheckBox("Extended encryption and "+
                                "authentication methods (VeNCrypt)");
    vencryptPane.add(secVeNCrypt);
    vencryptPane.add(Box.createHorizontalGlue());

    JPanel encrPanel = new JPanel(new GridBagLayout());
    encrPanel.setBorder(BorderFactory.createTitledBorder("Encryption"));
    encNone = new JCheckBox("None");
    encTLS = new JCheckBox("Anonymous TLS");
    encX509 = new JCheckBox("TLS with X.509 certificates");
    JLabel caLabel = new JLabel("X.509 CA Certificate");
    x509ca = new JTextField();
    x509ca.setName(Configuration.getParam("x509ca").getName());
    caButton = new JButton("Browse");
    JLabel crlLabel = new JLabel("X.509 CRL file");
    x509crl = new JTextField();
    x509crl.setName(Configuration.getParam("x509crl").getName());
    crlButton = new JButton("Browse");
    encrPanel.add(encNone,
                  new GridBagConstraints(0, 0,
                                         REMAINDER, 1,
                                         HEAVY, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(0, 0, 4, 0),
                                         NONE, NONE));
    encrPanel.add(encTLS,
                  new GridBagConstraints(0, 1,
                                         REMAINDER, 1,
                                         HEAVY, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(0, 0, 4, 0),
                                         NONE, NONE));
    encrPanel.add(encX509,
                  new GridBagConstraints(0, 2,
                                         3, 1,
                                         HEAVY, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(0, 0, 0, 0),
                                         NONE, NONE));
    indent = getButtonLabelInset(encX509);
    encrPanel.add(caLabel,
                  new GridBagConstraints(0, 3,
                                         1, 1,
                                         LIGHT, LIGHT,
                                         LINE_END, NONE,
                                         new Insets(0, indent, 5, 0),
                                         0, 0));
    encrPanel.add(x509ca,
                  new GridBagConstraints(1, 3,
                                         1, 1,
                                         HEAVY, LIGHT,
                                         LINE_START, HORIZONTAL,
                                         new Insets(0, 5, 5, 0),
                                         0, 0));
    encrPanel.add(caButton,
                  new GridBagConstraints(2, 3,
                                         1, 1,
                                         LIGHT, LIGHT,
                                         LINE_START, VERTICAL,
                                         new Insets(0, 5, 5, 0),
                                         0, 0));
    encrPanel.add(crlLabel,
                  new GridBagConstraints(0, 4,
                                         1, 1,
                                         LIGHT, LIGHT,
                                         LINE_END, NONE,
                                         new Insets(0, indent, 0, 0),
                                         0, 0));
    encrPanel.add(x509crl,
                  new GridBagConstraints(1, 4,
                                         1, 1,
                                         HEAVY, LIGHT,
                                         LINE_START, HORIZONTAL,
                                         new Insets(0, 5, 0, 0),
                                         0, 0));
    encrPanel.add(crlButton,
                  new GridBagConstraints(2, 4,
                                         1, 1,
                                         LIGHT, LIGHT,
                                         LINE_START, VERTICAL,
                                         new Insets(0, 5, 0, 0),
                                         0, 0));

    JPanel authPanel = new JPanel(new GridBagLayout());
    authPanel.setBorder(BorderFactory.createTitledBorder("Authentication"));

    secNone = new JCheckBox("None");
    secVnc = new JCheckBox("Standard VNC");
    secPlain = new JCheckBox("Plaintext");
    secIdent = new JCheckBox("Ident");
    sendLocalUsername = new JCheckBox("Send Local Username");
    authPanel.add(secNone,
                  new GridBagConstraints(0, 0,
                                         REMAINDER, 1,
                                         LIGHT, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(0, 0, 4, 0),
                                         NONE, NONE));
    authPanel.add(secVnc,
                  new GridBagConstraints(0, 1,
                                         REMAINDER, 1,
                                         LIGHT, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(0, 0, 4, 0),
                                         NONE, NONE));
    authPanel.add(secPlain,
                  new GridBagConstraints(0, 2,
                                         1, 1,
                                         LIGHT, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(0, 0, 2, 0),
                                         NONE, NONE));
    authPanel.add(secIdent,
                  new GridBagConstraints(0, 3,
                                         1, 1,
                                         LIGHT, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(2, 0, 0, 0),
                                         NONE, NONE));
    authPanel.add(sendLocalUsername,
                  new GridBagConstraints(1, 2,
                                         1, 2,
                                         HEAVY, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(2, 20, 2, 0),
                                         NONE, NONE));

    SecPanel.add(vencryptPane,
                 new GridBagConstraints(0, 0,
                                        REMAINDER, 1,
                                        LIGHT, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, 0, 4, 0),
                                        NONE, NONE));
    SecPanel.add(encrPanel,
                 new GridBagConstraints(0, 1,
                                        REMAINDER, 1,
                                        LIGHT, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, 0, 4, 0),
                                        NONE, NONE));
    SecPanel.add(authPanel,
                 new GridBagConstraints(0, 2,
                                        REMAINDER, 1,
                                        LIGHT, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, 0, 4, 0),
                                        NONE, NONE));
    SecPanel.add(Box.createRigidArea(new Dimension(0,0)),
                 new GridBagConstraints(0, RELATIVE,
                                        REMAINDER, REMAINDER,
                                        HEAVY, HEAVY,
                                        LINE_START, BOTH,
                                        new Insets(0, 0, 0, 0),
                                        NONE, NONE));

    // Input tab
    JPanel inputPanel = new JPanel(new GridBagLayout());
    inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));

    viewOnly = new JCheckBox("View Only (ignore mouse & keyboard)");
    acceptClipboard = new JCheckBox("Accept clipboard from server");
    sendClipboard = new JCheckBox("Send clipboard to server");
    JLabel menuKeyLabel = new JLabel("Menu Key");
    String[] menuKeys = new String[MenuKey.getMenuKeySymbolCount()];
    for (int i = 0; i < MenuKey.getMenuKeySymbolCount(); i++)
      menuKeys[i] = MenuKey.getKeyText(MenuKey.getMenuKeySymbols()[i]);
    menuKey  = new JComboBox(menuKeys);

    inputPanel.add(viewOnly,
                   new GridBagConstraints(0, 0,
                                          REMAINDER, 1,
                                          HEAVY, LIGHT,
                                          LINE_START, NONE,
                                          new Insets(0, 0, 4, 0),
                                          NONE, NONE));
    inputPanel.add(acceptClipboard,
                   new GridBagConstraints(0, 1,
                                          REMAINDER, 1,
                                          HEAVY, LIGHT,
                                          LINE_START, NONE,
                                          new Insets(0, 0, 4, 0),
                                          NONE, NONE));
    inputPanel.add(sendClipboard,
                   new GridBagConstraints(0, 2,
                                          REMAINDER, 1,
                                          HEAVY, LIGHT,
                                          LINE_START, NONE,
                                          new Insets(0, 0, 4, 0),
                                          NONE, NONE));
    inputPanel.add(menuKeyLabel,
                   new GridBagConstraints(0, 3,
                                          1, 1,
                                          LIGHT, LIGHT,
                                          LINE_START, NONE,
                                          new Insets(0, 0, 0, 0),
                                          NONE, NONE));
    inputPanel.add(menuKey,
                   new GridBagConstraints(1, 3,
                                          1, 1,
                                          HEAVY, LIGHT,
                                          LINE_START, NONE,
                                          new Insets(0, 5, 0, 0),
                                          NONE, NONE));
    inputPanel.add(Box.createRigidArea(new Dimension(5, 0)),
                   new GridBagConstraints(0, 4,
                                          REMAINDER, REMAINDER,
                                          HEAVY, HEAVY,
                                          LINE_START, BOTH,
                                          new Insets(0, 0, 0, 0),
                                          NONE, NONE));

    // Screen tab
    JPanel ScreenPanel = new JPanel(new GridBagLayout());
    ScreenPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
    desktopSize = new JCheckBox("Resize remote session on connect");
    desktopSize.setEnabled(!cc.viewer.embed.getValue() &&
                           (cc.viewer.desktopSize.getValue() != null));
    desktopWidth = new IntegerTextField(5);
    desktopWidth.setEnabled(desktopSize.isSelected());
    desktopHeight = new IntegerTextField(5);
    desktopHeight.setEnabled(desktopSize.isSelected());
    JPanel desktopSizePanel =
      new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
    desktopSizePanel.add(desktopWidth);
    desktopSizePanel.add(new JLabel(" x "));
    desktopSizePanel.add(desktopHeight);
    fullScreen = new JCheckBox("Full-screen mode");
    fullScreen.setEnabled(!cc.viewer.embed.getValue());
    fullScreenAllMonitors =
      new JCheckBox("Enable full-screen mode over all monitors");
    fullScreenAllMonitors.setEnabled(!cc.viewer.embed.getValue());
    JLabel scalingFactorLabel = new JLabel("Scaling Factor");
    Object[] scalingFactors = {
      "Auto", "Fixed Aspect Ratio", "50%", "75%", "95%", "100%", "105%",
      "125%", "150%", "175%", "200%", "250%", "300%", "350%", "400%" };
    scalingFactor = new MyJComboBox(scalingFactors);
    scalingFactor.setEditable(true);
    scalingFactor.setEnabled(!cc.viewer.embed.getValue());
    ScreenPanel.add(desktopSize,
                    new GridBagConstraints(0, 0,
                                           REMAINDER, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, 0, 0, 0),
                                           NONE, NONE));
    indent = getButtonLabelInset(desktopSize);
    ScreenPanel.add(desktopSizePanel,
                    new GridBagConstraints(0, 1,
                                           REMAINDER, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, indent, 0, 0),
                                           NONE, NONE));
    ScreenPanel.add(fullScreen,
                    new GridBagConstraints(0, 2,
                                           REMAINDER, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, 0, 4, 0),
                                           NONE, NONE));
    indent = getButtonLabelInset(fullScreen);
    ScreenPanel.add(fullScreenAllMonitors,
                    new GridBagConstraints(0, 3,
                                           REMAINDER, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, indent, 4, 0),
                                           NONE, NONE));
    ScreenPanel.add(scalingFactorLabel,
                    new GridBagConstraints(0, 4,
                                           1, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, 0, 4, 0),
                                           NONE, NONE));
    ScreenPanel.add(scalingFactor,
                    new GridBagConstraints(1, 4,
                                           1, 1,
                                           HEAVY, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, 5, 4, 0),
                                           NONE, NONE));
    ScreenPanel.add(Box.createRigidArea(new Dimension(5, 0)),
                    new GridBagConstraints(0, 5,
                                           REMAINDER, REMAINDER,
                                           HEAVY, HEAVY,
                                           LINE_START, BOTH,
                                           new Insets(0, 0, 0, 0),
                                           NONE, NONE));

    // Misc tab
    JPanel MiscPanel = new JPanel(new GridBagLayout());
    MiscPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
    shared =
      new JCheckBox("Shared connection (do not disconnect other viewers)");
    useLocalCursor = new JCheckBox("Render cursor locally");
    acceptBell = new JCheckBox("Beep when requested by the server");
    MiscPanel.add(shared,
                  new GridBagConstraints(0, 0,
                                         1, 1,
                                         LIGHT, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(0, 0, 4, 0),
                                         NONE, NONE));
    MiscPanel.add(useLocalCursor,
                  new GridBagConstraints(0, 1,
                                         1, 1,
                                         LIGHT, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(0, 0, 4, 0),
                                         NONE, NONE));
    MiscPanel.add(acceptBell,
                  new GridBagConstraints(0, 2,
                                         1, 1,
                                         LIGHT, LIGHT,
                                         LINE_START, NONE,
                                         new Insets(0, 0, 4, 0),
                                         NONE, NONE));
    MiscPanel.add(Box.createRigidArea(new Dimension(5, 0)),
                  new GridBagConstraints(0, 3,
                                         REMAINDER, REMAINDER,
                                         HEAVY, HEAVY,
                                         LINE_START, BOTH,
                                         new Insets(0, 0, 0, 0),
                                         NONE, NONE));

    // SSH tab
    JPanel sshPanel = new JPanel(new GridBagLayout());
    sshPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
    sshTunnel = new JCheckBox("Tunnel VNC over SSH");

    JPanel tunnelPanel = new JPanel(new GridBagLayout());

    sshUseGateway = new JCheckBox("Use SSH gateway");
    JLabel sshUserLabel = new JLabel("Username");
    sshUser = new JTextField();
    JLabel sshUserAtLabel = new JLabel("@");
    JLabel sshHostLabel = new JLabel("Hostname (or IP address)");
    sshHost = new JTextField("");
    JLabel sshPortLabel = new JLabel("Port");
    sshPort = new IntegerTextField(5);

    sshUseExt = new JCheckBox("Use external SSH client");
    sshClient = new JTextField();
    sshClient.setName(Configuration.getParam("extSSHClient").getName());
    sshClientBrowser = new JButton("Browse");
    JLabel sshConfigLabel = new JLabel("SSH config file");
    sshConfig = new JTextField();
    sshConfig.setName(Configuration.getParam("sshConfig").getName());
    sshConfigBrowser = new JButton("Browse");
    JLabel sshKeyFileLabel = new JLabel("SSH identity file");
    sshKeyFile = new JTextField();
    sshKeyFile.setName(Configuration.getParam("sshKeyFile").getName());
    sshKeyFileBrowser = new JButton("Browse");
    JPanel sshArgsPanel = new JPanel(new GridBagLayout());
    JLabel sshArgsLabel = new JLabel("Arguments:");
    sshArgsDefault =
      new GroupedJRadioButton("Default", sshArgsGroup, sshArgsPanel);
    sshArgsCustom =
      new GroupedJRadioButton("Custom", sshArgsGroup, sshArgsPanel);
    sshArguments = new JTextField();

    JPanel gatewayPanel = new JPanel(new GridBagLayout());
    gatewayPanel.add(sshUseGateway,
                    new GridBagConstraints(0, 0,
                                           REMAINDER, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, 0, 4, 0),
                                           NONE, NONE));
    indent = getButtonLabelInset(sshUseGateway);
    gatewayPanel.add(sshUserLabel,
                 new GridBagConstraints(0, 1,
                                        1, 1,
                                        LIGHT, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, indent, 4, 0),
                                        NONE, NONE));
    gatewayPanel.add(sshHostLabel,
                 new GridBagConstraints(2, 1,
                                        1, 1,
                                        HEAVY, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, 0, 4, 0),
                                        NONE, NONE));
    gatewayPanel.add(sshPortLabel,
                 new GridBagConstraints(3, 1,
                                        1, 1,
                                        LIGHT, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, 5, 4, 0),
                                        NONE, NONE));
    gatewayPanel.add(sshUser,
                 new GridBagConstraints(0, 2,
                                        1, 1,
                                        LIGHT, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, indent, 0, 0),
                                        NONE, NONE));
    gatewayPanel.add(sshUserAtLabel,
                 new GridBagConstraints(1, 2,
                                        1, 1,
                                        LIGHT, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, 2, 0, 2),
                                        NONE, NONE));
    gatewayPanel.add(sshHost,
                 new GridBagConstraints(2, 2,
                                        1, 1,
                                        HEAVY, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, 0, 0, 0),
                                        NONE, NONE));
    gatewayPanel.add(sshPort,
                 new GridBagConstraints(3, 2,
                                        1, 1,
                                        LIGHT, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, 5, 0, 0),
                                        NONE, NONE));

    JPanel clientPanel = new JPanel(new GridBagLayout());
    clientPanel.add(sshUseExt,
                    new GridBagConstraints(0, 0,
                                           1, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, 0, 0, 0),
                                           NONE, NONE));
    clientPanel.add(sshClient,
                    new GridBagConstraints(1, 0,
                                           1, 1,
                                           HEAVY, LIGHT,
                                           LINE_START, HORIZONTAL,
                                           new Insets(0, 5, 0, 0),
                                           NONE, NONE));
    clientPanel.add(sshClientBrowser,
                    new GridBagConstraints(2, 0,
                                           1, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, 5, 0, 0),
                                           NONE, NONE));
    sshArgsPanel.add(sshArgsLabel,
                    new GridBagConstraints(0, 1,
                                           1, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, 0, 0, 0),
                                           NONE, NONE));
    sshArgsPanel.add(sshArgsDefault,
                    new GridBagConstraints(1, 1,
                                           1, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, 5, 0, 0),
                                           NONE, NONE));
    sshArgsPanel.add(sshArgsCustom,
                    new GridBagConstraints(2, 1,
                                           1, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, NONE,
                                           new Insets(0, 5, 0, 0),
                                           NONE, NONE));
    sshArgsPanel.add(sshArguments,
                    new GridBagConstraints(3, 1,
                                           1, 1,
                                           HEAVY, LIGHT,
                                           LINE_START, HORIZONTAL,
                                           new Insets(0, 5, 0, 0),
                                           NONE, NONE));
    indent = getButtonLabelInset(sshUseExt);
    clientPanel.add(sshArgsPanel,
                    new GridBagConstraints(0, 1,
                                           REMAINDER, 1,
                                           LIGHT, LIGHT,
                                           LINE_START, HORIZONTAL,
                                           new Insets(4, indent, 0, 0),
                                           NONE, NONE));

    JPanel opensshPanel = new JPanel(new GridBagLayout());
    opensshPanel.setBorder(BorderFactory.createTitledBorder("Embedded SSH client configuration"));
    opensshPanel.add(sshConfigLabel,
                     new GridBagConstraints(0, 0,
                                            1, 1,
                                            LIGHT, LIGHT,
                                            LINE_START, NONE,
                                            new Insets(0, 0, 5, 0),
                                            NONE, NONE));
    opensshPanel.add(sshConfig,
                     new GridBagConstraints(1, 0,
                                            1, 1,
                                            HEAVY, LIGHT,
                                            LINE_START, HORIZONTAL,
                                            new Insets(0, 5, 5, 0),
                                            NONE, NONE));
    opensshPanel.add(sshConfigBrowser,
                     new GridBagConstraints(2, 0,
                                            1, 1,
                                            LIGHT, LIGHT,
                                            LINE_START, VERTICAL,
                                            new Insets(0, 5, 5, 0),
                                            NONE, NONE));
    opensshPanel.add(sshKeyFileLabel,
                     new GridBagConstraints(0, 1,
                                            1, 1,
                                            LIGHT, LIGHT,
                                            LINE_START, NONE,
                                            new Insets(0, 0, 0, 0),
                                            NONE, NONE));
    opensshPanel.add(sshKeyFile,
                     new GridBagConstraints(1, 1,
                                            1, 1,
                                            HEAVY, LIGHT,
                                            LINE_START, HORIZONTAL,
                                            new Insets(0, 5, 0, 0),
                                            NONE, NONE));
    opensshPanel.add(sshKeyFileBrowser,
                     new GridBagConstraints(2, 1,
                                            1, 1,
                                            LIGHT, LIGHT,
                                            LINE_START, VERTICAL,
                                            new Insets(0, 5, 0, 0),
                                            NONE, NONE));
    tunnelPanel.add(gatewayPanel,
                    new GridBagConstraints(0, 0,
                                           REMAINDER, 1,
                                           HEAVY, LIGHT,
                                           LINE_START, HORIZONTAL,
                                           new Insets(0, 0, 4, 0),
                                           NONE, NONE));
    tunnelPanel.add(clientPanel,
                    new GridBagConstraints(0, 1,
                                           REMAINDER, 1,
                                           HEAVY, LIGHT,
                                           LINE_START, HORIZONTAL,
                                           new Insets(0, 0, 4, 0),
                                           NONE, NONE));
    tunnelPanel.add(opensshPanel,
                    new GridBagConstraints(0, 2,
                                           REMAINDER, 1,
                                           HEAVY, LIGHT,
                                           LINE_START, HORIZONTAL,
                                           new Insets(0, 0, 0, 0),
                                           NONE, NONE));

    sshPanel.add(sshTunnel,
                 new GridBagConstraints(0, 0,
                                        REMAINDER, 1,
                                        LIGHT, LIGHT,
                                        LINE_START, NONE,
                                        new Insets(0, 0, 4, 0),
                                        NONE, NONE));
    indent = getButtonLabelInset(sshTunnel);
    sshPanel.add(tunnelPanel,
                 new GridBagConstraints(0, 2,
                                        REMAINDER, 1,
                                        LIGHT, LIGHT,
                                        LINE_START, HORIZONTAL,
                                        new Insets(0, indent, 4, 0),
                                        NONE, NONE));
    sshPanel.add(Box.createRigidArea(new Dimension(5, 0)),
                 new GridBagConstraints(0, RELATIVE,
                                        REMAINDER, REMAINDER,
                                        HEAVY, HEAVY,
                                        LINE_START, BOTH,
                                        new Insets(0, 0, 0, 0),
                                        NONE, NONE));

    // load/save tab
    JPanel loadSavePanel = new JPanel(new GridBagLayout());
    loadSavePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
    JPanel configPanel = new JPanel(new GridBagLayout());
    configPanel.
      setBorder(BorderFactory.createTitledBorder("Configuration File"));
    cfLoadButton = new JButton("Load");
    cfSaveAsButton = new JButton("Save As...");
    configPanel.add(cfLoadButton,
                    new GridBagConstraints(0, 0,
                                           1, 1,
                                           HEAVY, LIGHT,
                                           CENTER, HORIZONTAL,
                                           new Insets(0, 0, 5, 0),
                                           NONE, NONE));
    configPanel.add(cfSaveAsButton,
                    new GridBagConstraints(0, 1,
                                           1, 1,
                                           HEAVY, HEAVY,
                                           CENTER, HORIZONTAL,
                                           new Insets(0, 0, 0, 0),
                                           NONE, NONE));

    JPanel defaultsPanel = new JPanel(new GridBagLayout());
    defaultsPanel.setBorder(BorderFactory.createTitledBorder("Defaults"));
    defClearButton = new JButton("Clear");
    defReloadButton = new JButton("Reload");
    defSaveButton = new JButton("Save");
    defaultsPanel.add(defClearButton,
                      new GridBagConstraints(0, 0,
                                             1, 1,
                                             HEAVY, LIGHT,
                                             CENTER, HORIZONTAL,
                                             new Insets(0, 0, 5, 0),
                                             NONE, NONE));
    defaultsPanel.add(defReloadButton,
                      new GridBagConstraints(0, 1,
                                             1, 1,
                                             HEAVY, LIGHT,
                                             CENTER, HORIZONTAL,
                                             new Insets(0, 0, 5, 0),
                                             NONE, NONE));
    defaultsPanel.add(defSaveButton,
                      new GridBagConstraints(0, 2,
                                             1, 1,
                                             HEAVY, HEAVY,
                                             CENTER, HORIZONTAL,
                                             new Insets(0, 0, 0, 0),
                                             NONE, NONE));

    loadSavePanel.add(configPanel,
                      new GridBagConstraints(0, 0,
                                             1, 1,
                                             HEAVY, LIGHT,
                                             PAGE_START, HORIZONTAL,
                                             new Insets(0, 0, 0, 0),
                                             NONE, NONE));
    loadSavePanel.add(Box.createRigidArea(new Dimension(5, 0)),
                      new GridBagConstraints(1, 1,
                                             1, 1,
                                             LIGHT, LIGHT,
                                             LINE_START, NONE,
                                             new Insets(0, 0, 0, 0),
                                             NONE, NONE));
    loadSavePanel.add(defaultsPanel,
                      new GridBagConstraints(2, 0,
                                             1, 1,
                                             HEAVY, LIGHT,
                                             PAGE_START, HORIZONTAL,
                                             new Insets(0, 0, 0, 0),
                                             NONE, NONE));
    loadSavePanel.add(Box.createRigidArea(new Dimension(5, 0)),
                      new GridBagConstraints(0, 1,
                                             REMAINDER, REMAINDER,
                                             HEAVY, HEAVY,
                                             LINE_START, BOTH,
                                             new Insets(0, 0, 0, 0),
                                             NONE, NONE));

    // tabPane
    tabPane.addTab("Compression", FormatPanel);
    tabPane.addTab("Security", SecPanel);
    tabPane.addTab("Input", inputPanel);
    tabPane.addTab("Screen", ScreenPanel);
    tabPane.addTab("Misc", MiscPanel);
    tabPane.addTab("SSH", sshPanel);
    tabPane.addTab("Load / Save", loadSavePanel);
    tabPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
    // Resize the tabPane if necessary to prevent scrolling
    Insets tpi =
      (Insets)UIManager.get("TabbedPane:TabbedPaneTabArea.contentMargins");
    int minWidth = tpi.left + tpi.right;
    for (int i = 0; i < tabPane.getTabCount(); i++)
      minWidth += tabPane.getBoundsAt(i).width;
    int minHeight = tabPane.getPreferredSize().height;
    if (tabPane.getPreferredSize().width < minWidth)
      tabPane.setPreferredSize(new Dimension(minWidth, minHeight));

    // button pane
    okButton = new JButton("OK");
    cancelButton = new JButton("Cancel");

    JPanel buttonPane = new JPanel(new GridLayout(1, 5, 10, 10));
    buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5));
    buttonPane.add(Box.createRigidArea(new Dimension()));
    buttonPane.add(Box.createRigidArea(new Dimension()));
    buttonPane.add(Box.createRigidArea(new Dimension()));
    buttonPane.add(okButton);
    buttonPane.add(cancelButton);

    this.add(tabPane);
    this.add(buttonPane);
    addListeners(this);
    pack();
  }

  public void initDialog() {
    if (cc != null) cc.setOptions();
    zrle.setEnabled(!autoSelect.isSelected());
    hextile.setEnabled(!autoSelect.isSelected());
    tight.setEnabled(!autoSelect.isSelected());
    raw.setEnabled(!autoSelect.isSelected());
    fullColour.setEnabled(!autoSelect.isSelected());
    mediumColour.setEnabled(!autoSelect.isSelected());
    lowColour.setEnabled(!autoSelect.isSelected());
    veryLowColour.setEnabled(!autoSelect.isSelected());
    compressLevel.setEnabled(customCompressLevel.isSelected());
    qualityLevel.setEnabled(noJpeg.isSelected());
    sendLocalUsername.setEnabled(secVeNCrypt.isEnabled() &&
      (secPlain.isSelected() || secIdent.isSelected()));
    sshArguments.setEnabled(sshTunnel.isSelected() &&
      (sshUseExt.isSelected() && sshArgsCustom.isSelected()));
  }

  private void updatePreferences() {
    if (autoSelect.isSelected()) {
      UserPreferences.set("global", "AutoSelect", true);
    } else {
      UserPreferences.set("global", "AutoSelect", false);
      if (zrle.isSelected()) {
        UserPreferences.set("global", "PreferredEncoding", "ZRLE");
      } else if (hextile.isSelected()) {
        UserPreferences.set("global", "PreferredEncoding", "hextile");
      } else if (tight.isSelected()) {
        UserPreferences.set("global", "PreferredEncoding", "Tight");
      } else if (raw.isSelected()) {
        UserPreferences.set("global", "PreferredEncoding", "raw");
      }
    }
    if (fullColour.isSelected()) {
      UserPreferences.set("global", "FullColour", true);
    } else {
      UserPreferences.set("global", "FullColour", false);
      if (mediumColour.isSelected()) {
        UserPreferences.set("global", "LowColorLevel", 2);
      } else if (lowColour.isSelected()) {
        UserPreferences.set("global", "LowColorLevel", 1);
      } else if (veryLowColour.isSelected()) {
        UserPreferences.set("global", "LowColorLevel", 0);
      }
    }
    UserPreferences.set("global", "NoJPEG", !noJpeg.isSelected());
    UserPreferences.set("global",
            "QualityLevel", (Integer)qualityLevel.getSelectedItem());
    UserPreferences.set("global",
            "CustomCompressLevel", customCompressLevel.isSelected());
    UserPreferences.set("global",
            "CompressLevel", (Integer)compressLevel.getSelectedItem());
    UserPreferences.set("global", "ViewOnly", viewOnly.isSelected());
    UserPreferences.set("global",
            "AcceptClipboard", acceptClipboard.isSelected());
    UserPreferences.set("global", "SendClipboard", sendClipboard.isSelected());
    String menuKeyStr =
      MenuKey.getMenuKeySymbols()[menuKey.getSelectedIndex()].name;
    UserPreferences.set("global", "MenuKey", menuKeyStr);
    String desktopSizeString =
      desktopSize.isSelected() ?
        desktopWidth.getText() + "x" + desktopHeight.getText() : "";
    UserPreferences.set("global", "DesktopSize", desktopSizeString);
    UserPreferences.set("global", "FullScreen", fullScreen.isSelected());
    UserPreferences.set("global",
            "FullScreenAllMonitors", fullScreenAllMonitors.isSelected());
    UserPreferences.set("global", "Shared", shared.isSelected());
    UserPreferences.set("global",
            "UseLocalCursor", useLocalCursor.isSelected());
    UserPreferences.set("global", "AcceptBell", acceptBell.isSelected());
    String scaleString = scalingFactor.getSelectedItem().toString();
    if (scaleString.equalsIgnoreCase("Auto")) {
      UserPreferences.set("global", "ScalingFactor", "Auto");
    } else if(scaleString.equalsIgnoreCase("Fixed Aspect Ratio")) {
      UserPreferences.set("global", "ScalingFactor", "FixedRatio");
    } else {
      scaleString=scaleString.substring(0, scaleString.length()-1);
      UserPreferences.set("global", "ScalingFactor", scaleString);
    }
    UserPreferences.set("viewer", "secVeNCrypt", secVeNCrypt.isSelected());
    UserPreferences.set("viewer", "encNone", encNone.isSelected());
    UserPreferences.set("viewer", "encTLS", encTLS.isSelected());
    UserPreferences.set("viewer", "encX509", encX509.isSelected());
    UserPreferences.set("viewer", "secNone", secNone.isSelected());
    UserPreferences.set("viewer", "secVnc", secVnc.isSelected());
    UserPreferences.set("viewer", "secPlain", secPlain.isSelected());
    UserPreferences.set("viewer", "secIdent", secIdent.isSelected());
    UserPreferences.set("global",
            "SendLocalUsername", sendLocalUsername.isSelected());
    if (!CSecurityTLS.x509ca.getValueStr().equals(""))
      UserPreferences.set("viewer", "x509ca",
              CSecurityTLS.x509ca.getValueStr());
    if (!CSecurityTLS.x509crl.getValueStr().equals(""))
      UserPreferences.set("viewer", "x509crl",
              CSecurityTLS.x509crl.getValueStr());
    UserPreferences.set("global", "Tunnel", sshTunnel.isSelected());
    if (sshUseGateway.isSelected()) {
      String via = sshUser.getText()+"@"+sshHost.getText()+":"+sshPort.getText();
      UserPreferences.set("global", "Via", via);
    }
    if (sshUseExt.isSelected()) {
      UserPreferences.set("global", "extSSH", sshUseExt.isSelected());
      UserPreferences.set("global", "extSSHClient", sshClient.getText());
      if (!sshArguments.getText().isEmpty())
        UserPreferences.set("global", "extSSHArgs", sshArguments.getText());
    }
    UserPreferences.set("global", "SSHConfig", sshConfig.getText());
    UserPreferences.set("global", "SSHKeyFile", sshKeyFile.getText());
  }

  private void restorePreferences() {
    autoSelect.setSelected(UserPreferences.getBool("global", "AutoSelect"));
    if (!autoSelect.isSelected()) {
      if (UserPreferences.getBool("global", "FullColour")) {
        fullColour.setSelected(true);
      } else {
        switch (UserPreferences.getInt("global", "LowColorLevel")) {
        case 2:
          mediumColour.setSelected(true);
          break;
        case 1:
          lowColour.setSelected(true);
          break;
        case 0:
          veryLowColour.setSelected(true);
          break;
        }
      }
      String encoding = UserPreferences.get("global", "PreferredEncoding");
      if (encoding != null) {
        switch (Encodings.encodingNum(encoding)) {
        case Encodings.encodingZRLE:
          zrle.setSelected(true);
          break;
        case Encodings.encodingHextile:
          hextile.setSelected(true);
          break;
        case Encodings.encodingRaw:
          raw.setSelected(true);
          break;
        default:
          tight.setSelected(true);
        }
      }
    }
    noJpeg.setSelected(!UserPreferences.getBool("global", "NoJPEG"));
    qualityLevel.setSelectedItem(UserPreferences.getInt("global",
            "QualityLevel"));
    customCompressLevel.setSelected(UserPreferences.getBool("global",
            "CustomCompressLevel"));
    compressLevel.setSelectedItem(UserPreferences.getInt("global",
            "CompressLevel"));
    viewOnly.setSelected(UserPreferences.getBool("global", "ViewOnly"));
    acceptClipboard.setSelected(UserPreferences.getBool("global",
            "AcceptClipboard"));
    sendClipboard.setSelected(UserPreferences.getBool("global",
            "SendClipboard"));
    menuKey.setSelectedItem(UserPreferences.get("global", "MenuKey"));
    desktopSize.setSelected(!UserPreferences.get("global", "DesktopSize").isEmpty());
    if (desktopSize.isSelected()) {
      String desktopSizeString = UserPreferences.get("global", "DesktopSize");
      desktopWidth.setText(desktopSizeString.split("x")[0]);
      desktopHeight.setText(desktopSizeString.split("x")[1]);
    }
    fullScreen.setSelected(UserPreferences.getBool("global", "FullScreen"));
    fullScreenAllMonitors.setSelected(UserPreferences.getBool("global",
            "FullScreenAllMonitors"));
    if (shared.isEnabled())
      shared.setSelected(UserPreferences.getBool("global", "Shared"));
    useLocalCursor.setSelected(UserPreferences.getBool("global",
            "UseLocalCursor"));
    acceptBell.setSelected(UserPreferences.getBool("global", "AcceptBell"));
    String scaleString = UserPreferences.get("global", "ScalingFactor");
    if (scaleString != null) {
      if (scaleString.equalsIgnoreCase("Auto")) {
        scalingFactor.setSelectedItem("Auto");
      } else if (scaleString.equalsIgnoreCase("FixedRatio")) {
        scalingFactor.setSelectedItem("Fixed Aspect Ratio");
      } else {
        scalingFactor.setSelectedItem(scaleString+"%");
      }
    }
    if (secVeNCrypt.isEnabled()) {
      secVeNCrypt.setSelected(UserPreferences.getBool("viewer",
              "secVeNCrypt", true));
      if (secVeNCrypt.isSelected()) {
        encNone.setSelected(UserPreferences.getBool("viewer", "encNone", true));
        encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true));
        encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true));
        secPlain.setSelected(UserPreferences.getBool("viewer", "secPlain", true));
        secIdent.setSelected(UserPreferences.getBool("viewer", "secIdent", true));
        sendLocalUsername.setSelected(UserPreferences.getBool("global",
                "SendLocalUsername"));
      }
    }
    if (secNone.isEnabled())
      secNone.setSelected(UserPreferences.getBool("viewer", "secNone", true));
    if (secVnc.isEnabled())
      secVnc.setSelected(UserPreferences.getBool("viewer", "secVnc", true));
    sshTunnel.setSelected(UserPreferences.getBool("global", "Tunnel"));
    sshUseGateway.setSelected(UserPreferences.get("global", "Via") != null);
    if (sshUseGateway.isSelected())
      cc.viewer.via.setParam(UserPreferences.get("global", "Via"));
    sshUser.setText(Tunnel.getSshUser(cc));
    sshHost.setText(Tunnel.getSshHost(cc));
    sshPort.setText(Integer.toString(Tunnel.getSshPort(cc)));
    sshUseExt.setSelected(UserPreferences.getBool("global", "extSSH"));
    File f = new File(UserPreferences.get("global", "extSSHClient"));
    if (f.exists() && f.canExecute())
      sshClient.setText(f.getAbsolutePath());
    sshArguments.setText(UserPreferences.get("global", "extSSHArgs"));
    if (sshArguments.getText().isEmpty())
      sshArgsDefault.setSelected(true);
    else
      sshArgsCustom.setSelected(true);
    f = new File(UserPreferences.get("global", "SSHConfig"));
    if (f.exists() && f.canRead())
      sshConfig.setText(f.getAbsolutePath());
    if (UserPreferences.get("global", "SSHKeyFile") != null) {
      f = new File(UserPreferences.get("global", "SSHKeyFile"));
      if (f.exists() && f.canRead())
        sshKeyFile.setText(f.getAbsolutePath());
    } else {
      sshKeyFile.setText(Tunnel.getSshKeyFile(cc));
    }
    sshUseGateway.setEnabled(sshTunnel.isSelected());
    sshUser.setEnabled(sshTunnel.isSelected() &&
                       sshUseGateway.isEnabled() &&
                       sshUseGateway.isSelected());
    sshHost.setEnabled(sshTunnel.isSelected() &&
                       sshUseGateway.isEnabled() &&
                       sshUseGateway.isSelected());
    sshPort.setEnabled(sshTunnel.isSelected() &&
                       sshUseGateway.isEnabled() &&
                       sshUseGateway.isSelected());
    sshUseExt.setEnabled(sshTunnel.isSelected());
    sshClient.setEnabled(sshTunnel.isSelected() &&
                         sshUseExt.isEnabled());
    sshClientBrowser.setEnabled(sshTunnel.isSelected() &&
                                sshUseExt.isEnabled() &&
                                sshUseExt.isSelected());
    sshArgsDefault.setEnabled(sshTunnel.isSelected() &&
                              sshUseExt.isEnabled() &&
                              sshUseExt.isSelected());
    sshArgsCustom.setEnabled(sshTunnel.isSelected() &&
                             sshUseExt.isEnabled() &&
                             sshUseExt.isSelected());
    sshArguments.setEnabled(sshTunnel.isSelected() &&
                            sshUseExt.isEnabled() &&
                            sshUseExt.isSelected() &&
                            sshArgsCustom.isSelected());
    sshConfig.setEnabled(sshTunnel.isSelected() &&
                         sshUseExt.isEnabled() &&
                         !sshUseExt.isSelected());
    sshConfigBrowser.setEnabled(sshTunnel.isSelected() &&
                                sshUseExt.isEnabled() &&
                                !sshUseExt.isSelected());
    sshKeyFile.setEnabled(sshTunnel.isSelected() &&
                          sshUseExt.isEnabled() &&
                          !sshUseExt.isSelected());
    sshKeyFileBrowser.setEnabled(sshTunnel.isSelected() &&
                                 sshUseExt.isEnabled() &&
                                 !sshUseExt.isSelected());
  }

  public void endDialog() {
    super.endDialog();
    if (cc.viewport != null && cc.viewport.isVisible()) {
      cc.viewport.toFront();
      cc.viewport.requestFocus();
    }
  }

  public void actionPerformed(ActionEvent e) {
    Object s = e.getSource();
    if (s instanceof JButton) {
      JButton button = (JButton)s;
      if (button == okButton) {
        JTextField[] fields =
          { x509ca, x509crl, sshClient, sshConfig, sshKeyFile };
        for (JTextField field : fields) {
          if (field.getText() != null && !field.getText().equals("")) {
            File f = new File(field.getText());
            if (!f.exists() || !f.canRead()) {
              String msg = new String("The file "+f.getAbsolutePath()+
                           " specified for option "+field.getName()+
                           " does not exist or cannot be read.  Please"+
                           " correct before proceeding.");
              JOptionPane.showMessageDialog(this, msg, "WARNING",
                                            JOptionPane.WARNING_MESSAGE);
              return;
            }
          }
        }
        if (cc != null) cc.getOptions();
        endDialog();
      } else if (button == cancelButton) {
        endDialog();
      } else if (button == cfLoadButton) {
        JFileChooser fc = new JFileChooser();
        fc.setDialogTitle("Path to configuration file");
        fc.setApproveButtonText("OK");
        fc.setFileHidingEnabled(false);
        int ret = fc.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION) {
          String filename = fc.getSelectedFile().toString();
          if (filename != null)
            Configuration.load(filename);
          cc.setOptions();
        }
      } else if (button == cfSaveAsButton) {
        JFileChooser fc = new JFileChooser();
        fc.setDialogTitle("Save current configuration as:");
        fc.setApproveButtonText("OK");
        fc.setFileHidingEnabled(false);
        int ret = fc.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION) {
          String filename = fc.getSelectedFile().toString();
          if (filename != null)
            Configuration.save(filename);
        }
      } else if (button == defSaveButton) {
        updatePreferences();
        UserPreferences.save();
      } else if (button == defReloadButton) {
        restorePreferences();
      } else if (button == defClearButton) {
        UserPreferences.clear();
        cc.setOptions();
      } else if (button == caButton) {
        JFileChooser fc =
          new JFileChooser(new File(CSecurityTLS.getDefaultCA()));
        fc.setDialogTitle("Path to X509 CA certificate");
        fc.setApproveButtonText("OK");
        fc.setFileHidingEnabled(false);
        int ret = fc.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION)
          x509ca.setText(fc.getSelectedFile().toString());
      } else if (button == crlButton) {
        JFileChooser fc =
          new JFileChooser(new File(CSecurityTLS.getDefaultCRL()));
        fc.setDialogTitle("Path to X509 CRL file");
        fc.setApproveButtonText("OK");
        fc.setFileHidingEnabled(false);
        int ret = fc.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION)
          x509crl.setText(fc.getSelectedFile().toString());
      } else if (button == sshClientBrowser) {
        JFileChooser fc = new JFileChooser();
        fc.setDialogTitle("Path to external SSH client");
        fc.setApproveButtonText("OK");
        fc.setFileHidingEnabled(false);
        int ret = fc.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION)
          sshClient.setText(fc.getSelectedFile().toString());
      } else if (button == sshConfigBrowser) {
        JFileChooser fc = new JFileChooser();
        fc.setDialogTitle("Path to OpenSSH client config file");
        fc.setApproveButtonText("OK");
        fc.setFileHidingEnabled(false);
        int ret = fc.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION)
          sshConfig.setText(fc.getSelectedFile().toString());
      } else if (button == sshKeyFileBrowser) {
        JFileChooser fc = new JFileChooser();
        fc.setDialogTitle("Path to SSH key file");
        fc.setApproveButtonText("OK");
        fc.setFileHidingEnabled(false);
        int ret = fc.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION)
          sshKeyFile.setText(fc.getSelectedFile().toString());
      }
    } else if (s instanceof JRadioButton) {
      JRadioButton button = (JRadioButton)s;
      if (button == sshArgsCustom || button == sshArgsDefault) {
        sshArguments.setEnabled(sshArgsCustom.isSelected());
      }
    }
  }

  public void itemStateChanged(ItemEvent e) {
    Object s = e.getSource();
    if (s instanceof JCheckBox) {
      JCheckBox item = (JCheckBox)s;
      boolean enable = item.isSelected();
      if (item == autoSelect) {
        ButtonGroup[] groups = { encodingGroup, colourGroup };
        for (ButtonGroup grp : groups) {
          Enumeration<AbstractButton> elems = grp.getElements();
          while (elems.hasMoreElements())
            elems.nextElement().setEnabled(!enable);
        }
      } else if (item == customCompressLevel) {
        compressLevel.setEnabled(enable);
      } else if (item == desktopSize) {
        desktopWidth.setEnabled(enable);
        desktopHeight.setEnabled(enable);
      } else if (item == noJpeg) {
        qualityLevel.setEnabled(enable);
      } else if (item == encX509) {
        x509ca.setEnabled(enable);
        caButton.setEnabled(enable);
        x509crl.setEnabled(enable);
        crlButton.setEnabled(enable);
      } else if (item == secVeNCrypt) {
        encNone.setEnabled(enable);
        encTLS.setEnabled(enable);
        encX509.setEnabled(enable);
        x509ca.setEnabled(enable && encX509.isSelected());
        caButton.setEnabled(enable && encX509.isSelected());
        x509crl.setEnabled(enable && encX509.isSelected());
        crlButton.setEnabled(enable && encX509.isSelected());
        secIdent.setEnabled(enable);
        secPlain.setEnabled(enable);
        sendLocalUsername.setEnabled(enable);
      } else if (item == encNone) {
        secNone.setSelected(enable &&
          UserPreferences.getBool("viewer", "secNone", true));
        secVnc.setSelected(enable &&
          UserPreferences.getBool("viewer", "secVnc", true));
      } else if (item == secIdent || item == secPlain) {
        sendLocalUsername.setEnabled(secIdent.isSelected() ||
                                     secPlain.isSelected());
      } else if (item == sshTunnel) {
        sshUseGateway.setEnabled(enable);
        sshUser.setEnabled(enable &&
                           sshUseGateway.isEnabled() &&
                           sshUseGateway.isSelected());
        sshHost.setEnabled(enable &&
                           sshUseGateway.isEnabled() &&
                           sshUseGateway.isSelected());
        sshPort.setEnabled(enable &&
                           sshUseGateway.isEnabled() &&
                           sshUseGateway.isSelected());
        sshUseExt.setEnabled(enable);
        sshClient.setEnabled(enable &&
                             sshUseExt.isEnabled() &&
                             sshUseExt.isSelected());
        sshClientBrowser.setEnabled(enable &&
                                    sshUseExt.isEnabled() &&
                                    sshUseExt.isSelected());
        sshArgsDefault.setEnabled(enable &&
                                  sshUseExt.isEnabled() &&
                                  sshUseExt.isSelected());
        sshArgsCustom.setEnabled(enable &&
                                 sshUseExt.isEnabled() &&
                                 sshUseExt.isSelected());
        sshArguments.setEnabled(enable &&
                                sshUseExt.isEnabled() &&
                                sshUseExt.isSelected() &&
                                sshArgsCustom.isSelected());
        sshConfig.setEnabled(enable &&
                             sshUseExt.isEnabled() &&
                             !sshUseExt.isSelected());
        sshConfigBrowser.setEnabled(enable &&
                                    sshUseExt.isEnabled() &&
                                    !sshUseExt.isSelected());
        sshKeyFile.setEnabled(enable &&
                              sshUseExt.isEnabled() &&
                              !sshUseExt.isSelected());
        sshKeyFileBrowser.setEnabled(enable &&
                                     sshUseExt.isEnabled() &&
                                     !sshUseExt.isSelected());
      } else if (item == sshUseExt) {
        sshClient.setEnabled(enable);
        sshClientBrowser.setEnabled(enable);
        sshArgsDefault.setEnabled(enable);
        sshArgsCustom.setEnabled(enable);
        sshArguments.setEnabled(enable && sshArgsCustom.isSelected());
        sshConfig.setEnabled(!enable);
        sshConfigBrowser.setEnabled(!enable);
        sshKeyFile.setEnabled(!enable);
        sshKeyFileBrowser.setEnabled(!enable);
      } else if (item == sshUseGateway) {
        sshUser.setEnabled(enable);
        sshHost.setEnabled(enable);
        sshPort.setEnabled(enable);
      }
    }
  }
}
