001    package edu.harvard.deas.hyperenc.gui;
002    
003    import java.awt.Component;
004    import java.awt.Container;
005    import java.awt.Frame;
006    import java.awt.GridBagConstraints;
007    import java.awt.GridBagLayout;
008    import java.awt.event.ActionEvent;
009    import java.awt.event.ActionListener;
010    import java.awt.event.WindowAdapter;
011    import java.awt.event.WindowEvent;
012    
013    import javax.mail.Address;
014    import javax.mail.internet.AddressException;
015    import javax.mail.internet.InternetAddress;
016    import javax.swing.BorderFactory;
017    import javax.swing.JButton;
018    import javax.swing.JDialog;
019    import javax.swing.JLabel;
020    import javax.swing.JOptionPane;
021    import javax.swing.JPanel;
022    import javax.swing.JScrollPane;
023    import javax.swing.JTextArea;
024    import javax.swing.JTextField;
025    import javax.swing.border.EtchedBorder;
026    
027    import edu.harvard.deas.hyperenc.Contact;
028    import edu.harvard.deas.hyperenc.util.HexCoder;
029    
030    /**
031     * A dialog box for adding a new contact.
032     */
033    public class AddContact extends JDialog { 
034            
035            private static final long serialVersionUID = 1L;
036      private JLabel displayNameLabel, fullNameLabel, emailAddrLabel;
037      private JTextField displayName, fullName, emailAddr;
038      private JTextArea outSecret, inSecret;
039      private JButton add, cancel;
040      private Contact newGuy;
041      
042      private byte[] outSecretBytes = null;
043      private byte[] inSecretBytes = null;
044    
045      /**
046       * Create a new Add Contact dialog.
047       * @param owner the frame that owns this dialog
048       */
049      public AddContact(Frame owner) {
050        this(owner, "", "", "");
051      }
052    
053      /**
054       * Create a new Add Contact dialog with the name and contact fields
055       * initialized according to the given parameters.
056       * 
057       * @param owner the frame that owns this dialog
058       * @param initAddr the initial value of the <i>e-mail contact</i> field
059       * @param initFullName the initial value of the <i>full name</i> field
060       * @param initDisplayName the initial value of the <i>display name</i> field
061       */
062            public AddContact(Frame owner, String initAddr, String initFullName,
063                        String initDisplayName) {
064                    super(owner, "Enter new contact info");
065    
066                    setModal(true);
067                    setSize(400, 400);
068                    setLocation(150, 150);
069    
070                    addWindowListener(new WindowAdapter() {
071          public void windowClosing(WindowEvent e) {
072            setVisible(false);  // TODO call cancel instead
073          }
074        });
075    
076                    // A simple header to let the user know what's going on
077                    displayNameLabel = new JLabel("Display Name:");
078                    displayNameLabel.setVerticalAlignment(JLabel.CENTER);
079                    displayNameLabel.setHorizontalAlignment(JLabel.LEFT);
080    
081                    displayName = new JTextField(30);
082        displayName.setText(initDisplayName);
083    
084                    // A simple header to let the user know what's going on 
085                    fullNameLabel = new JLabel("Full Name:");
086                    fullNameLabel.setVerticalAlignment(JLabel.CENTER);
087                    fullNameLabel.setHorizontalAlignment(JLabel.LEFT);
088    
089                    fullName = new JTextField(30);
090        fullName.setText(initFullName);
091    
092                    // A simple header to let the user know what's going on 
093                    emailAddrLabel = new JLabel("Email Address:");
094                    emailAddrLabel.setVerticalAlignment(JLabel.CENTER);
095                    emailAddrLabel.setHorizontalAlignment(JLabel.LEFT);
096    
097                    emailAddr = new JTextField(30);
098        emailAddr.setText(initAddr);
099        
100        // Shared secret entry boxes
101        JLabel outSecretLabel = new JLabel("Outgoing secret:");
102        outSecretLabel.setVerticalAlignment(JLabel.TOP);
103        outSecretLabel.setHorizontalAlignment(JLabel.LEFT);
104        JLabel inSecretLabel = new JLabel("Incoming secret:");
105        inSecretLabel.setVerticalAlignment(JLabel.TOP);
106        inSecretLabel.setHorizontalAlignment(JLabel.LEFT);
107        
108        outSecret = new JTextArea(3, 80);
109        inSecret = new JTextArea(3, 80);
110        JScrollPane outSecretPane = new JScrollPane(outSecret);
111        JScrollPane inSecretPane = new JScrollPane(inSecret);
112        
113        outSecret.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
114        outSecret.setLineWrap(true);
115        
116        inSecret.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
117        inSecret.setLineWrap(true);
118    
119                    // Create the two main buttons
120        JPanel buttonPanel = new JPanel();
121        
122                    add = new JButton("Add");
123                    cancel = new JButton("Cancel");
124                    
125                    buttonPanel.add(add);
126                    buttonPanel.add(cancel);
127    
128                    // Attempts to make a Contact from the provided information. If successful,
129        // stores the new Contact in newGuy and closes this dialog. If validation of
130        // the entered information fails, displays an appropriate error message.
131                    add.addActionListener(new ActionListener() {
132                            public void actionPerformed(ActionEvent e) {
133                              if (emailAddr.getText().trim().equals("")) {
134                                JOptionPane.showMessageDialog(null,
135                                    "The e-mail contact field may not be left blank",
136                                    "Error",
137                  JOptionPane.ERROR_MESSAGE);
138                                return;
139                              }
140                              
141                              Address emailAddress;
142                              try {
143                                emailAddress = makeAddress(emailAddr.getText());
144                              } catch (AddressException ex) {
145                                JOptionPane.showMessageDialog(null,
146                                    "The provided e-mail contact is invalid",
147                                    "Error",
148                                    JOptionPane.ERROR_MESSAGE);
149                                return;
150                              }
151                              
152                              String outString = outSecret.getText();
153                              String inString = inSecret.getText();
154                              
155                              if (outString.isEmpty() || inString.isEmpty()) {
156                                JOptionPane.showMessageDialog(null,
157                  "Both outgoing and incoming secret must be specified.",
158                  "Error",
159                  JOptionPane.ERROR_MESSAGE);
160              return;
161                              }
162                              
163                              byte[] os;
164                              try {
165                                if (outString.length() % 2 == 1)
166                                  outString += "0";
167                                os = HexCoder.decode(outString);
168                              } catch (IllegalArgumentException ex) {
169                                JOptionPane.showMessageDialog(null,
170                  "The outgoing secret may only contain characters 0-9 or a-f.",
171                  "Error",
172                  JOptionPane.ERROR_MESSAGE);
173              return;
174                              }
175                              
176                              byte[] is;
177                              try {
178              if (inString.length() % 2 == 1)
179                inString += "0";
180              is = HexCoder.decode(inString);
181                              } catch (IllegalArgumentException ex) {
182                                JOptionPane.showMessageDialog(null,
183                  "The incoming secret may only contain characters 0-9 or a-f.",
184                  "Error",
185                  JOptionPane.ERROR_MESSAGE);
186              return;
187                              }
188                              
189                              outSecretBytes = os;
190                              inSecretBytes = is;
191                              
192                              newGuy = makeContact(displayName.getText(), fullName.getText(), emailAddress);
193                              setVisible(false);
194                            }
195                    });
196    
197    
198                    cancel.addActionListener(new ActionListener() {
199                            public void actionPerformed(ActionEvent e) {
200                                    newGuy = null;
201                                    outSecretBytes = null;
202                                    inSecretBytes = null;
203                              setVisible(false);
204                            }
205                    });
206    
207                    
208                    // Add all components to the frame
209                    
210                    Container pane = this.getContentPane();
211                    pane.setLayout(new GridBagLayout());
212        GridBagConstraints gridConstraints = new GridBagConstraints();
213                    
214                    // An empty label to control the size of the grid bag by padding the top
215        JLabel padTop = new JLabel("");
216        addItem(padTop, gridConstraints, 0, 0, 4, 1, GridBagConstraints.HORIZONTAL,
217                GridBagConstraints.CENTER, 0, 0);
218        
219        // An empty label to control the size of the grid bag by padding the bottom
220        JLabel padBottom = new JLabel("");
221        addItem(padBottom, gridConstraints, 0, 9, 4, 1, GridBagConstraints.HORIZONTAL,
222                GridBagConstraints.CENTER, 0, 0);
223        
224        // An empty label to control the size of the grid bag by padding the left side
225        JLabel padLeft = new JLabel("");
226        addItem(padLeft, gridConstraints, 0, 1, 1, 8, GridBagConstraints.VERTICAL,
227                GridBagConstraints.CENTER, 0, 0);
228        
229        // An empty label to control the size of the grid bag by padding the right side
230        JLabel padRight = new JLabel("");
231        addItem(padRight, gridConstraints, 3, 1, 1, 8, GridBagConstraints.VERTICAL,
232                GridBagConstraints.CENTER, 0, 0);
233        
234        addItem(displayNameLabel, gridConstraints, 1, 1, 1, 1, GridBagConstraints.NONE,
235                GridBagConstraints.CENTER, 0, 0);
236        
237        addItem(displayName, gridConstraints, 2, 1, 1, 1, GridBagConstraints.HORIZONTAL,
238                GridBagConstraints.CENTER, 1.0, 0);
239        
240        addItem(fullNameLabel, gridConstraints, 1, 2, 1, 1, GridBagConstraints.NONE, 
241            GridBagConstraints.CENTER, 0, 0);
242        
243        addItem(fullName, gridConstraints, 2, 2, 1, 1, GridBagConstraints.HORIZONTAL, 
244            GridBagConstraints.CENTER, 1.0, 0);
245        
246        addItem(emailAddrLabel, gridConstraints, 1, 3, 1, 1, GridBagConstraints.NONE, 
247            GridBagConstraints.CENTER, 0, 0);
248        
249        addItem(emailAddr, gridConstraints, 2, 3, 1, 1, GridBagConstraints.HORIZONTAL, 
250            GridBagConstraints.CENTER, 1.0, 0);
251        
252        addItem(outSecretLabel, gridConstraints, 1, 4, 1, 1, GridBagConstraints.NONE, 
253            GridBagConstraints.CENTER, 0, 1.0);
254        
255        addItem(outSecretPane, gridConstraints, 2, 4, 1, 1, GridBagConstraints.BOTH, 
256            GridBagConstraints.CENTER, 1.0, 1.0);
257        
258        addItem(inSecretLabel, gridConstraints, 1, 5, 1, 1, GridBagConstraints.NONE, 
259            GridBagConstraints.CENTER, 0, 1.0);
260        
261        addItem(inSecretPane, gridConstraints, 2, 5, 1, 1, GridBagConstraints.BOTH, 
262            GridBagConstraints.CENTER, 1.0, 1.0);
263        
264        addItem(buttonPanel, gridConstraints, 1, 7, 2, 1, GridBagConstraints.HORIZONTAL, 
265            GridBagConstraints.CENTER, 0, 0);
266        
267        this.getRootPane().setDefaultButton(add);
268        
269                    setVisible(true);
270            }
271    
272      /**
273       * Returns the new Contact constructed from the data entered in this form. If
274       * the dialog was canceled by the user, for example via the Cancel button or
275       * by closing the window by other means, then return <code>null</code>.
276       * 
277       * @return a new Contact constructed from the data entered in this form, or
278       *         <code>null</code> no contact was created
279       */
280            public Contact getNewContact() {
281                    return newGuy;
282            }
283    
284      /**
285       * Returns the outgoing shared secret entered by the user, as a byte array.
286       * Returns <code>null</code> under the same conditions as
287       * <code>getNewContact</code>.
288       * 
289       * @return shared secret for outgoing communication, as a byte array
290       */
291            public byte[] getOutgoingSecret() {
292              return outSecretBytes;
293            }
294            
295            /**
296       * Returns the incoming shared secret entered by the user, as a byte array.
297       * Returns <code>null</code> under the same conditions as
298       * <code>getNewContact</code>.
299       * 
300       * @return shared secret for incoming communication, as a byte array
301       */
302            public byte[] getIncomingSecret() {
303              return inSecretBytes;
304      }
305    
306      /**
307       * Creates and returns a Contact. If <i>display name</i> or <i>full name</i>
308       * contains nothing but whitespace, it is set to a textual representation of
309       * the e-mail contact.
310       * 
311       * @param displayName the display name of the new contact
312       * @param fullName the full name of the new contact
313       * @param email the e-mail contact of the new contact
314       * @return the created Contact 
315       */
316            private Contact makeContact(String display, String full, Address email) {
317                    if (display.trim().equals("")) {
318          display = email.toString();
319                    }
320    
321                    if (full.trim().equals("")) {
322          full = "("+emailAddr.getText()+")";
323                    }
324    
325                    Contact c = new Contact(full, email, display);
326        return c;
327            }
328    
329      /**
330       * Tries to create an Address from the given string and return it.
331       * 
332       * @param s
333       *        String containing an e-mail contact. Leading and trailing whitespace
334       *        is trimmed.
335       * @return the created Address
336       * @throws AddressException if the contact creation failed
337       */
338            private Address makeAddress(String s) throws AddressException {
339              InternetAddress a = new InternetAddress(s.trim());
340              a.validate();
341              return a;
342            }
343            
344    
345            /** Helper function for adding items to the grid bag layout */
346            // XXX order of arguments is impossible to remember, making this method prone
347      // to error; how can we improve it?
348            private void addItem(Component item, GridBagConstraints gc, 
349                                                     int xPosition, int yPosition, int width, int height,
350                                                     int fill, int anchor, double wx, double wy) {
351                    gc.gridx = xPosition;
352                    gc.gridy = yPosition;
353                    gc.gridwidth = width;
354                    gc.gridheight = height;
355                    gc.fill = fill; 
356                    gc.anchor = anchor;
357                    gc.weightx = wx; 
358                    gc.weighty = wy;
359    
360                    getContentPane().add(item, gc);
361            }
362    
363      /*
364       * Launches an AddContact dialog and prints the results to stdout. Useful for
365       * testing. 
366       */
367            /*
368            public static void main(String args[]) {
369              try {
370                UIManager.setLookAndFeel(
371                    UIManager.getSystemLookAndFeelClassName());
372              } catch (Exception e) {
373                throw new RuntimeException(e);
374              }
375              
376              AddContact ac = new AddContact(null);
377              Contact newContact = ac.getNewContact();
378              
379              if (newContact != null) {
380                Address email = newContact.getEmail();
381                String emailStr = "[invalid]";
382                if (email != null)
383                  email.toString();
384                
385              System.out.println("Display: " + newContact.getDisplayName());
386              System.out.println("Full: " + newContact.getFullName());
387              System.out.println("E-mail: " + email.toString());
388              System.out.println("Out secret: " + HexCoder.encode(ac.getOutgoingSecret()));
389              System.out.println("In secret: " + HexCoder.encode(ac.getIncomingSecret()));
390              }
391              
392              System.exit(0);
393            }
394      */
395    }