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 }