View Javadoc
1   /*
2    * Copyright (C) 2005-2015 Schlichtherle IT Services.
3    * All rights reserved. Use is subject to license terms.
4    */
5   package net.java.truecommons.key.swing;
6   
7   import net.java.truecommons.key.swing.io.FileComboBoxBrowser;
8   
9   import javax.annotation.Nullable;
10  import javax.swing.*;
11  import javax.swing.filechooser.FileSystemView;
12  import javax.swing.text.Document;
13  import javax.swing.text.JTextComponent;
14  import java.awt.*;
15  import java.awt.event.WindowEvent;
16  import java.awt.event.WindowFocusListener;
17  import java.io.File;
18  import java.util.ResourceBundle;
19  
20  /**
21   * A panel displaying a password panel or a key file panel in order to let
22   * the user select an authentication method and enter a key.
23   *
24   * @since  TrueCommons 2.2
25   * @author Christian Schlichtherle
26   */
27  public final class AuthenticationPanel extends JPanel {
28  
29      private static final long serialVersionUID = 0L;
30  
31      private static final ResourceBundle resources = ResourceBundle
32              .getBundle(AuthenticationPanel.class.getName());
33  
34      /** The password authentication method. */
35      static final int AUTH_PASSWD = 0;
36  
37      /** The key file authentication method. */
38      static final int AUTH_KEY_FILE = 1;
39  
40      private final FileComboBoxBrowser fcbb;
41  
42      public AuthenticationPanel() {
43          // Order is important here: The file combo box browser installs its
44          // own editor, so we have to adjust the columns last.
45          fcbb = new FileComboBoxBrowser(keyFile);
46          initComponents();
47          ((JTextField) keyFile.getEditor().getEditorComponent()).setColumns(30);
48      }
49  
50      /**
51       * Returns the file system view which is used for the key file combo box
52       * and its associated file chooser.
53       * If this property has never been initialized or has been explicitly set
54       * to {@code null}, then a call to this method reinitializes it by calling
55       * {@link FileSystemView#getFileSystemView}.
56       */
57      public FileSystemView getFileSystemView() {
58          return fcbb.getFileSystemView();
59      }
60  
61      /**
62       * Sets the file system view which is used for the key file combo box
63       * and its associated file chooser.
64       */
65      public void setFileSystemView(@Nullable FileSystemView fsv) {
66          fcbb.setFileSystemView(fsv);
67      }
68  
69      /**
70       * Returns the directory which is used for the key file combo box
71       * and its associated file chooser.
72       * If this property has never been initialized or has been explicitly set
73       * to {@code null}, then a call to this method reinitializes it by calling
74       * {@link FileSystemView#getDefaultDirectory} on the
75       * {@linkplain #getFileSystemView file system view}.
76       */
77      public File getDirectory() { return fcbb.getDirectory(); }
78  
79      /**
80       * Sets the directory which is used for the key file combo box and its
81       * associated file chooser.
82       */
83      public void setDirectory(@Nullable File dir) {
84          fcbb.setDirectory(dir);
85      }
86  
87      /**
88       * Sets the panel which should be used to enter the password.
89       *
90       * @param  passwdPanel the password panel.
91       * @throws NullPointerException If {@code passwdPanel} is
92       *         {@code null}.
93       */
94      public void setPasswdPanel(final JPanel passwdPanel) {
95          passwdPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
96          final String title = resources.getString("tab.passwd");
97          if (title.equals(tabs.getTitleAt(AUTH_PASSWD)))
98              tabs.removeTabAt(AUTH_PASSWD);
99          tabs.insertTab(title, null, passwdPanel, null, AUTH_PASSWD); // NOI18N
100         tabs.setSelectedIndex(AUTH_PASSWD);
101         revalidate();
102     }
103 
104     Document getKeyFileDocument() {
105         return ((JTextComponent) keyFile.getEditor().getEditorComponent()).getDocument();
106     }
107 
108     /**
109      * Returns the key file.
110      *
111      * @return The key file.
112      */
113     File getKeyFile() {
114         String path = (String) keyFile.getSelectedItem();
115         File file = new File(path);
116         return file.isAbsolute() ? file : new File(getDirectory(), path);
117     }
118 
119     private void setKeyFile(final File file) {
120         String newPath = file.getPath();
121         {
122             final String dir = getDirectory().getPath();
123             if (newPath.startsWith(dir))
124                 newPath = newPath.substring(dir.length() + 1); // cut off file separator, too.
125         }
126         final String oldPath = (String) keyFile.getSelectedItem();
127         if (newPath.equals(oldPath)) return;
128         keyFile.setSelectedItem(newPath);
129     }
130 
131     /**
132      * Returns the authentication method selected by the user.
133      *
134      * @return {@code AUTH_PASSWD} or {@code AUTH_KEY_FILE}.
135      */
136     int getAuthenticationMethod() {
137         final int method = tabs.getSelectedIndex();
138         switch (method) {
139             case AUTH_PASSWD:
140                 assert resources.getString("tab.passwd").equals(tabs.getTitleAt(method));
141                 break;
142             case AUTH_KEY_FILE:
143                 assert resources.getString("tab.keyFile").equals(tabs.getTitleAt(method));
144                 break;
145             default:
146                 throw new AssertionError("Unsupported authentication method!");
147         }
148         return method;
149     }
150 
151     /** This method is called from within the constructor to
152      * initialize the form.
153      * WARNING: Do NOT modify this code. The content of this method is
154      * always regenerated by the Form Editor.
155      */
156     // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
157     private void initComponents() {
158         java.awt.GridBagConstraints gridBagConstraints;
159 
160         final net.java.truecommons.key.swing.util.EnhancedPanel keyFilePanel = new net.java.truecommons.key.swing.util.EnhancedPanel();
161         final javax.swing.JLabel keyFileLabel = new javax.swing.JLabel();
162         final javax.swing.JButton keyFileChooser = new javax.swing.JButton();
163 
164         setLayout(new java.awt.GridBagLayout());
165 
166         keyFilePanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
167         keyFilePanel.addPanelListener(new net.java.truecommons.key.swing.util.PanelListener() {
168             public void ancestorWindowShown(net.java.truecommons.key.swing.util.PanelEvent evt) {
169                 keyFilePanelAncestorWindowShown(evt);
170             }
171             public void ancestorWindowHidden(net.java.truecommons.key.swing.util.PanelEvent evt) {
172             }
173         });
174         keyFilePanel.setLayout(new java.awt.GridBagLayout());
175 
176         keyFileLabel.setDisplayedMnemonic(resources.getString("keyFile").charAt(0));
177         keyFileLabel.setLabelFor(keyFile);
178         keyFileLabel.setText(resources.getString("keyFile")); // NOI18N
179         gridBagConstraints = new java.awt.GridBagConstraints();
180         gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
181         gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
182         keyFilePanel.add(keyFileLabel, gridBagConstraints);
183 
184         keyFile.setEditable(true);
185         gridBagConstraints = new java.awt.GridBagConstraints();
186         gridBagConstraints.gridx = 0;
187         gridBagConstraints.gridy = 1;
188         gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
189         gridBagConstraints.weightx = 1.0;
190         keyFilePanel.add(keyFile, gridBagConstraints);
191 
192         keyFileChooser.setIcon(UIManager.getIcon("FileView.directoryIcon"));
193         keyFileChooser.setToolTipText(resources.getString("selectKeyFile.toolTip")); // NOI18N
194         keyFileChooser.setName("keyFileChooser"); // NOI18N
195         keyFileChooser.addActionListener(new java.awt.event.ActionListener() {
196             public void actionPerformed(java.awt.event.ActionEvent evt) {
197                 keyFileChooserActionPerformed(evt);
198             }
199         });
200         gridBagConstraints = new java.awt.GridBagConstraints();
201         gridBagConstraints.gridx = 1;
202         gridBagConstraints.gridy = 1;
203         gridBagConstraints.insets = new java.awt.Insets(0, 10, 0, 0);
204         keyFilePanel.add(keyFileChooser, gridBagConstraints);
205 
206         tabs.addTab(resources.getString("tab.keyFile"), keyFilePanel); // NOI18N
207 
208         gridBagConstraints = new java.awt.GridBagConstraints();
209         gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
210         gridBagConstraints.weightx = 1.0;
211         gridBagConstraints.weighty = 1.0;
212         add(tabs, gridBagConstraints);
213     }// </editor-fold>//GEN-END:initComponents
214 
215     private void keyFileChooserActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_keyFileChooserActionPerformed
216         final JFileChooser fc = newFileChooser();
217         if (JFileChooser.APPROVE_OPTION == fc.showOpenDialog(this))
218             setKeyFile(fc.getSelectedFile());
219     }//GEN-LAST:event_keyFileChooserActionPerformed
220 
221     private JFileChooser newFileChooser() {
222         final JFileChooser fc = new JFileChooser(getDirectory());
223         fc.setDialogTitle(resources.getString("fileChooser.title"));
224         fc.setFileHidingEnabled(false);
225         return fc;
226     }
227 
228     private void keyFilePanelAncestorWindowShown(net.java.truecommons.key.swing.util.PanelEvent evt) {//GEN-FIRST:event_keyFilePanelAncestorWindowShown
229         // These are the things I hate Swing for: All I want to do here is to
230         // set the focus to the keyFile field in this panel when it shows.
231         // However, this can't be done in the constructor since the panel is
232         // not yet placed in a window which is actually showing.
233         // Right, then I use this event listener to do it. This listener
234         // method is called when the ancestor window is showing (and coding
235         // the event generation was a less than trivial task).
236         // But wait, simply setting the focus in this event listener here is
237         // not possible on Linux because the containing window (now there is
238         // one) didn't gain the focus yet.
239         // Strangely enough, this works on Windows.
240         // Even more strange, not even calling passwd.requestFocus() makes it
241         // work on Linux!
242         // So I add a window focus listener here and remove it when a Focus
243         // Gained Event occurs.
244         // But wait, then I still can't request the focus: This time it
245         // doesn't work on Windows, while it works on Linux.
246         // I still don't know the reason why, but it seems it's moving too
247         // fast, so I have to post a new event to the event queue which finally
248         // sets the focus.
249         // But wait, requesting the focus could still fail for some strange,
250         // undocumented reason - I wouldn't be surprised anymore.
251         // So I add a conditional to select the entire contents of the field
252         // only if I can really transfer the focus to it.
253         // Otherwise, users could get easily confused.
254         // If you carefully read the documentation for requestFocusInWindow()
255         // however, then you learn that even if it returns true, there is still
256         // no guarantee that the focus gets actually transferred...
257         // This mess is insane!
258         final Window window = evt.getSource().getAncestorWindow();
259         window.addWindowFocusListener(new WindowFocusListener() {
260             @Override
261             public void windowGainedFocus(WindowEvent e) {
262                 window.removeWindowFocusListener(this);
263                 EventQueue.invokeLater(new Runnable() {
264                     @Override
265                     public void run() {
266                         if (keyFile.requestFocusInWindow())
267                             ((JTextComponent) keyFile.getEditor().getEditorComponent()).selectAll();
268                     }
269                 });
270             }
271 
272             @Override
273             public void windowLostFocus(WindowEvent e) {
274             }
275         });
276     }//GEN-LAST:event_keyFilePanelAncestorWindowShown
277 
278     // Variables declaration - do not modify//GEN-BEGIN:variables
279     private final javax.swing.JComboBox<String> keyFile = new javax.swing.JComboBox<String>();
280     private final javax.swing.JTabbedPane tabs = new javax.swing.JTabbedPane();
281     // End of variables declaration//GEN-END:variables
282 }