package client;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import msgs.*;
import java.util.*;
import org.jdesktop.jdic.tray.*;
import javax.sound.sampled.*;
import java.util.*;
/**
* Brings up the TigerTalk client GUI window, allows for login and network selection
* Team 9: Andrew Hayworth, Brian Parrella, Ryan Kortmann, Nina Papa
* @author Andrew Hayworth
*/
// Had to comment out tray code last minute -- something broke!! :-(
public class TTClient implements TTConstants {
protected static boolean soundOk = true;
private JComboBox jcb;
private String[] ntwks = { "TigerTalk", "AOL Instant Messenger" };
private JFrame blWindow;
private JTextField uNameField, hostnameField;
private JPasswordField pwdField;
private JLabel uNameLabel, pwdLabel, hostnameLabel;
private JPanel jp1, jp2, jp3, jp4, jp5, jp6;
private JButton login, cancel;
private int p1, p2;
/**
* logout menu item, we need to access this from a few places
*/
protected static JMenuItem miLogout;
/**
* System tray icon
*/
//protected static org.jdesktop.jdic.tray.TrayIcon trayIcon; // use the fully qualified package name as not to confuse JDK's newer than 5
/**
* Server ip address
*/
protected static String server;
/**
* Our username
*/
protected static String name;
/**
* Tigertalk login authentication key
*/
protected static byte[] key;
/**
* Our IP address
*/
protected static String ip;
/**
* Our password.
*/
protected static String pwd;
/**
* Buddylist data
*/
protected static String[] blData;
/**
* Status of each buddy in the list
*/
protected static HashMap<String, Integer> blDataStatus;
/**
* Boolean to determine if the TTRunningClient should update it's buddylist
*/
protected static boolean updateBLNeeded = false;
/**
* Running list of all the group chats
*/
protected static HashMap<String, Chat> chats;
/**
* Running list of the private messages
*/
protected static HashMap<String, PMChat> pmChats;
/**
* Determines if we were in the middle of updating our profile
*/
protected static boolean updatingProfile = false;
/**
* Locations of our sound files
*/
protected static URL loginSound, logoutSound, msgInSound, msgOutSound;
// this is a work-around table of os'es that tend to report that their external ip address is localhost...
private ArrayList<String> ipWorkaroundTable;
public static void main(String[] args) {
new TTClient();
}
// forcing one to read my gui code is forbidden by the geneva convention
public TTClient() {
System.out.println("Running on " + System.getProperty("os.name"));
// Some OS X specific gui niceness here
if (System.getProperty("os.name").startsWith("Mac")) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
System.setProperty("apple.laf.useScreenMenuBar", "true");
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "TigerTalk");
System.setProperty("com.apple.mrj.application.growbox.intrudes", "false");
}
catch (Exception e) {e.printStackTrace();} // this is ok, because on exception it just defaults to swing l&f
}
ipWorkaroundTable = createWorkaroundTable();
loginSound = getClass().getResource("sound/login.wav");
logoutSound = getClass().getResource("sound/logout.wav");
msgInSound = getClass().getResource("sound/msgin.wav");
msgOutSound = getClass().getResource("sound/msgout.wav");
pmChats = new HashMap<String, PMChat>();
chats = new HashMap<String, Chat>();
// where we're connecting on. Server can be run on different ports but the client, by default, cannot.
p1 = 6666;
p2 = 6667;
blDataStatus = new HashMap<String, Integer>();
blWindow = new JFrame();
uNameLabel = new JLabel("Username");
uNameField = new JTextField(10);
pwdLabel = new JLabel("Password");
pwdField = new JPasswordField(10);
hostnameField = new JTextField(10);
hostnameField.setText("localhost");
hostnameLabel = new JLabel("Server");
login = new JButton("Login");
login.setMnemonic('L');
cancel = new JButton("Cancel");
cancel.setMnemonic('C');
jcb = new JComboBox(ntwks);
blWindow.setLayout(new BoxLayout(blWindow.getContentPane(), BoxLayout.Y_AXIS));
jp2 = new JPanel();
jp3 = new JPanel();
jp4 = new JPanel();
jp5 = new JPanel();
jp6 = new JPanel();
jp2.setMaximumSize(new Dimension(210,20));
jp3.setMaximumSize(new Dimension(210,20));
jp4.setMaximumSize(new Dimension(210,20));
jp5.setMaximumSize(new Dimension(210,20));
jp6.setMaximumSize(new Dimension(210,20));
jp2.add(uNameLabel);
jp2.add(uNameField);
jp3.add(pwdLabel);
jp3.add(pwdField);
jp4.add(hostnameLabel);
jp4.add(hostnameField);
jp5.add(jcb);
jp6.add(login);
jp6.add(cancel);
blWindow.add(Box.createVerticalGlue());
blWindow.add(jp2);
blWindow.add(jp3);
blWindow.add(jp4);
blWindow.add(jp5);
blWindow.add(jp6);
blWindow.add(Box.createVerticalGlue());
blWindow.setSize(250, 600);
blWindow.setLocationRelativeTo(null);
blWindow.setTitle("TigerTalk v1.0 -- Login");
blWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
blWindow.setVisible(true);
createTrayIcon();
///////////
//// HERE BE KEYLISTENERS
///////////
login.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
login.doClick();
}
}
});
jcb.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
login.doClick();
}
}
});
cancel.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
cancel.doClick();
}
}
});
uNameField.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
login.doClick();
}
}
});
pwdField.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
login.doClick();
}
}
});
hostnameField.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
login.doClick();
}
}
});
// sets the hostname field to aol or localhost depending on the selection in jcb
// hackish, but it works :)
jcb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (jcb.getSelectedItem().toString().equals("AOL Instant Messenger")) {
jp4.setVisible(false);
hostnameField.setText("aol");
}
else {
jp4.setVisible(true);
hostnameField.setText("localhost");
}
}
});
// this starts the login process
login.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
try {
if (hostnameField.getText().equals("aol")) {
String p = "";
for (char c : pwdField.getPassword()) {
p += c;
}
// if we're trying to do aol, gtfo of this class and start AIM
(new AIM(uNameField.getText(), p)).start();
blWindow.dispose();
return;
}
// otherwise, try to raise a ttserver
login();
// provided we logged in, start our processing queues
startServerListener();
startServerSender();
startMsgParser();
PMSender ps = new PMSender(1234);
ps.start();
PMListener pl = new PMListener(1234);
pl.start();
PMParser pmp = new PMParser();
pmp.start();
blWindow.dispose();
// start the main show!
(new TTRunningClient(blData)).start();
}
catch (LoginException e) {
JOptionPane.showMessageDialog(null, e.getMessage(), "Login failed!", JOptionPane.ERROR_MESSAGE);
}
}
});
cancel.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
System.exit(0);
}
});
}
// This exists solely that I can have a nice table of OS'es that we need to hack the ip address for...
private ArrayList<String> createWorkaroundTable() {
ArrayList<String> a = new ArrayList<String>();
// essentially, we only need to add the unices, as java returns correct IP addr info for NT platforms and OS X.
// i'm not sure if it's a bug or feature that we're working around, but it is sure annoying.
a.add("Linux");
a.add("Solaris");
a.add("SunOS");
a.add("MPE/iX");
a.add("HP-UX");
a.add("HP UX");
a.add("AIX");
a.add("FreeBSD");
a.add("Irix");
a.add("Digital Unix");
return a;
}
// some convience methods for starting listeners ...
// originally created with the hopes that they would do more complex setup, but they never really did.
protected void startServerSender() {
(new ServerSender(p1)).start();
}
protected void startServerListener() {
(new ServerListener(p2)).start();
}
private void startMsgParser() {
(new MsgParser()).start();
}
// our private login method. handles getting the ip address, logging into the server, raising exceptions on failure and
// setting up our initial environment
private void login() throws LoginException{
try {
server = hostnameField.getText();
// open a socket and server socket to talk to TigerTalk
Socket sock = new Socket(server, p1);
ServerSocket ssock = new ServerSocket(p2);
// try to get the ip address ....if we're on a bad os :(
if (ipWorkaroundTable.contains(System.getProperty("os.name"))) {
System.out.println("Running on a unix, trying to get proper IP address info..");
// this is really braindamaged.... but its the easiest way...
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
NetworkInterface n = (NetworkInterface) e.nextElement();
// Apple, I truly hate thee. Why must thou ship JDK 1.5?
// Thou eliminatest many convenient methods from mine aresenal...
//if (!(n.isLoopback()) && n.isUp()) {
Enumeration<InetAddress> ei = n.getInetAddresses();
while (ei.hasMoreElements()) {
InetAddress i = (InetAddress) ei.nextElement();
if (!(i.isLoopbackAddress())) {
if (!(Character.isDigit(i.getHostAddress().charAt(0)))) System.out.println("IPv6 skipped");
else {
ip = i.getHostAddress();
System.out.println("Detected IP address: " + ip);
}
}
}
}
}
// yay, we're not on a bad os, this is simple now!
else {
InetAddress inetAddr = InetAddress.getLocalHost();
ip = inetAddr.getHostAddress();
System.out.println("Detected IP address: " + ip);
}
String p = "";
for (char c : pwdField.getPassword()) {
p += c;
}
// set up our login request message
ClientMsg cm = new ClientMsg(ip, uNameField.getText(), p);
cm.setAction(Actions.LOGIN);
ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
oos.writeObject(cm); //sendit
oos.flush();
oos.close();
// wait for a response
sock = ssock.accept();
ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
cm = (ClientMsg) ois.readObject();
ois.close();
sock.close();
ssock.close();
// if no go, throw a login exception back up to the actionlistener on the login button, and let them try again
if (cm.getResponse() == Responses.DENY) {
throw new LoginException("Username or password incorrect.");
}
// otherwise, setup our initial variables
name = cm.getName();
key = cm.getClientKey();
pwd = cm.getPwd();
String[] s = { "" };
blData = s;
blDataStatus.put("", 0);
}
catch (IOException e) {
throw new LoginException("Unable to contact server.");
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* Plays the login sound
*/
public static void playLogin() {
if (soundOk) {
try {
Clip soundClip = AudioSystem.getClip();
AudioInputStream ais = AudioSystem.getAudioInputStream(TTClient.loginSound);
soundClip.open(ais);
soundClip.start();
}
catch (Exception e) { e.printStackTrace(); }
}
}
/**
* Plays the logout sound
*/
public static void playLogout() {
if (soundOk) {
try {
Clip soundClip = AudioSystem.getClip();
AudioInputStream ais = AudioSystem.getAudioInputStream(TTClient.logoutSound);
soundClip.open(ais);
soundClip.start();
}
catch (Exception e) { e.printStackTrace(); }
}
}
/**
* Plays the message in sound
*/
public static void playMsgIn() {
if (soundOk) {
try {
Clip soundClip = AudioSystem.getClip();
AudioInputStream ais = AudioSystem.getAudioInputStream(TTClient.msgInSound);
soundClip.open(ais);
soundClip.start();
}
catch (Exception e) { e.printStackTrace(); }
}
}
/**
* Plays the message out sound
*/
public static void playMsgOut() {
if (soundOk) {
try {
Clip soundClip = AudioSystem.getClip();
AudioInputStream ais = AudioSystem.getAudioInputStream(TTClient.msgOutSound);
soundClip.open(ais);
soundClip.start();
}
catch (Exception e) { e.printStackTrace(); }
}
}
/**
* This creates our nifty little tray icon!
*/
public void createTrayIcon()
{
JPopupMenu pmMenu = new JPopupMenu();
JMenuItem miExit = new JMenuItem("Exit");
JMenuItem miAbout = new JMenuItem("About");
miLogout = new JMenuItem("Logout");
miLogout.setEnabled(false);
pmMenu.add(miAbout);
pmMenu.addSeparator();
pmMenu.add(miLogout);
pmMenu.add(miExit);
// will this work in a jar??
Image iconImage = Toolkit.getDefaultToolkit().getImage(getClass().getResource("images/tigertalk.png"));
ImageIcon iic = new ImageIcon(iconImage);
// Have to package this into an icon, or we can't use the JDIC
//trayIcon = new TrayIcon((Icon)iic, "TigerTalk v1.0", pmMenu);
// trayIcon = new org.jdesktop.jdic.tray.TrayIcon((Icon)iic, "TigerTalk v1.0", pmMenu);
// trayIcon.setIconAutoSize(true);
// kills things off cleanly if they try to close the program from this menu
miExit.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
if (name != null ) {
System.out.println("Shutting down!");
ServerSender.killServerSender();
ServerListener.killServerListener();
MsgParser.killMsgParser();
PMListener.killPMListener();
PMSender.killPMSender();
PMParser.killPMParser();
}
System.exit(0);
}
});
miAbout.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
//JDIC doesn't support this on the mac yet :'-(
if (System.getProperty("os.name").startsWith("Mac")) {
JOptionPane.showMessageDialog(null, "TigerTalk v1.0", "About TigerTalk", JOptionPane.INFORMATION_MESSAGE);
}
else {
// trayIcon.displayMessage("About TigerTalk v1.0", "219 Final Project\nMay 21st, 2009\nInfo box or this kind of message box for the about screen???", org.jdesktop.jdic.tray.TrayIcon.INFO_MESSAGE_TYPE);
}
//JOptionPane.showMessageDialog(null, "TigerTalk v1.0", "About TigerTalk", JOptionPane.INFORMATION_MESSAGE);
}
});
// using full classnames. Here's the reason:
// JDK 6 has native, fullfeatured classes for SystemTray and TrayIcon. Work wonderfully!
// ... but OS X ships JDK5. Which has neither class. In any way shape or form.
// So, we found an old library that the JDK 6 features were *based* from. It works rather well, and is cross platform
// even on an alpha build, which we are using, it is stable.
// however, to avoid confusing a JDK6 compiler with identical classnames, we're fully specifying them here.
// It's the same situation as in java.util.Timer vs javax.swing.Timer -- just ambiguous
// org.jdesktop.jdic.tray.SystemTray tray = org.jdesktop.jdic.tray.SystemTray.getDefaultSystemTray();
try
{
// tray.addTrayIcon(trayIcon);
}
catch (Exception e)
{
System.out.println("TrayIcon could not be added.");
}
}
/**
* @return the soundOk
*/
public static boolean isSoundOk() {
return soundOk;
}
/**
* @param soundOk the soundOk to set
*/
public static void setSoundOk(boolean soundOk) {
TTClient.soundOk = soundOk;
}
}
/**
* This class just allows us to barf up an error to the main gui when logging in
* @author Andrew Hayworth
*/
class LoginException extends Exception {
public LoginException() {
super();
}
public LoginException(String cause) {
super(cause);
}
}
|