/*
* ChatPane.java
*
* Created on Apr 5, 2009, 2:13:23 PM
*
* This file is a part of Shoddy Battle.
* Copyright (C) 2009 Catherine Fitzpatrick and Benjamin Gwin
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, visit the Free Software Foundation, Inc.
* online at http://gnu.org.
*/
package shoddybattleclient;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JScrollPane;
import shoddybattleclient.LobbyWindow.Channel;
import shoddybattleclient.utils.CloseableTabbedPane.CloseableTab;
import shoddybattleclient.utils.HTMLPane;
import shoddybattleclient.utils.Text;
/**
*
* @author ben
*/
public class ChatPane extends javax.swing.JPanel implements CloseableTab {
public static class CommandException extends Exception {
public CommandException(String message) {
super(message);
}
}
private static boolean m_logging = Preference.getAutosaveChatLogs();
private static final GregorianCalendar CALENDAR = new GregorianCalendar();
private HTMLPane m_chatPane;
private LobbyWindow m_lobby;
private String m_name;
private LobbyWindow.Channel m_channel;
private String m_sub;
private int m_tabCount = 0;
//hour of the last message received
private int m_lastTime = CALENDAR.get(Calendar.HOUR);
private PrintWriter m_log;
public LobbyWindow getLobby() {
return m_lobby;
}
/** Creates new form ChatPane */
public ChatPane(LobbyWindow.Channel c, LobbyWindow lobby, String name) {
m_channel = c;
m_lobby = lobby;
m_name = name;
initComponents();
txtChat.setFocusTraversalKeysEnabled(false);
m_chatPane = new HTMLPane();
scrollChat.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollChat.add(m_chatPane);
scrollChat.setViewportView(m_chatPane);
m_chatPane.addKeyListener(new KeyListener() {
public void keyReleased(KeyEvent evt) {}
public void keyPressed(KeyEvent evt) {}
public void keyTyped(KeyEvent evt) {
txtChat.requestFocusInWindow();
txtChat.dispatchEvent(evt);
}
});
initLogging();
}
private File getLogDir() {
String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = Preference.getLogDirectory() + "chat";
return new File(dir, date);
}
private void initLogging() {
if (m_channel.getType() == Channel.TYPE_BATTLE) return;
if (!m_logging) return;
File dir = getLogDir();
dir.mkdirs();
File f = new File(dir, m_channel.getName() + ".txt");
FileWriter out = null;
try {
out = new FileWriter(f, true);
m_log = new PrintWriter(out, true);
} catch (Exception e) {
e.printStackTrace();
}
}
public LobbyWindow.Channel getChannel() {
return m_channel;
}
private void parseCommand(String command, String args)
throws CommandException {
command = command.toLowerCase();
if ("mode".equals(command)) {
int idx = args.indexOf(' ');
String action, cmd;
if (idx == -1) {
action = args;
cmd = "";
} else {
action = args.substring(0, idx);
cmd = args.substring(idx + 1);
}
parseMode(action.toLowerCase(), cmd);
} else if ("ignore".equals(command)) {
if ("".equals(args.trim())) throw new CommandException("Usage: /ignore name");
boolean success = Preference.ignore(args);
if (!success) {
addMessage(null, "You are already ignoring " + args);
} else {
addMessage(null, "You are now ignoring " + args);
}
} else if ("unignore".equals(command)) {
if ("".equals(args.trim())) throw new CommandException("Usage: /unignore name");
boolean success = Preference.unignore(args);
if (!success) {
addMessage(null, "You are not ignoring " + args);
} else {
addMessage(null, "You are no longer ignoring " + args);
}
} else if ("ban".equals(command) ||
"gban".equals(command) ||
"ipban".equals(command)) {
String[] parts = args.split(",", 2);
int channel = "ban".equals(command) ? m_channel.getId() : -1;
boolean ipBan = "ipban".equals(command);
String user;
long d;
if (parts.length == 2) {
user = parts[0].trim();
String dateFormat = parts[1].trim();
d = parseDateFormat(dateFormat);
} else if (parts.length == 1) {
user = parts[0].trim();
BanDialog bd = new BanDialog(user);
bd.setVisible(true);
d = bd.getBanLength();
} else {
throw new CommandException(
"/[ban|gban|ipban] user, [[*y][*d][*h][*m]]");
}
if (d == 0) return;
m_lobby.getLink().sendBanMessage(channel, user, d, ipBan);
} else if ("gunban".equals(command)) {
String user = args.trim();
m_lobby.getLink().sendBanMessage(-1, user, -1, false);
} else if ("unban".equals(command)) {
String user = args.trim();
m_lobby.getLink().sendBanMessage(m_channel.getId(), user, -1, false);
} else if ("kick".equals(command)) {
String user = args.trim();
m_lobby.getLink().sendBanMessage(m_channel.getId(), user, 0, false);
} else if ("kill".equals(command)) {
String user = args.trim();
m_lobby.getLink().sendBanMessage(-1, user, 0, false);
} else if ("lookup".equals(command)) {
String user = args.trim();
m_lobby.getLink().requestUserLookup(user);
} else if ("msg".equals(command)) {
String user = args.trim();
m_lobby.openPrivateMessage(user, true);
} else if ("wall".equals(command)) {
String message = args.trim();
m_lobby.getLink().sendImportantMessage(-1, message);
} else if ("important".equals(command)) {
String message = args.trim();
m_lobby.getLink().sendImportantMessage(m_channel.getId(), message);
}
}
private int getMode(char c) {
switch (c) {
case 'a':
return 0;
case 'o':
return 1;
case 'v':
return 2;
case 'b':
return 3;
case 'm':
return 4;
case 'i':
return 5;
// todo: idle?
}
throw new InternalError();
}
private void parseMode(String action, String users) throws CommandException {
if ("".equals(action) || "help".equals(action)) {
throw new CommandException(
"Usage: /mode +a/o/v/b/m/i [user1[,user2,...]]");
}
char char1 = action.charAt(0);
if ((char1 != '+') && (char1 != '-')) {
throw new CommandException("Try '/mode help' for usage");
}
boolean add = (char1 == '+');
action = action.substring(1);
if (action.length() == 1) {
String user = users;
char c = action.charAt(0);
switch (c) {
case 'a':
case 'o':
case 'v':
case 'b':
case 'm':
case 'i':
int mode = getMode(c);
m_lobby.getLink().updateMode(m_channel.getId(),
user, mode, add);
break;
default:
throw new CommandException("Invalid command: " + action);
}
} else {
String[] args = users.split(",");
for (int i = 0; i < action.length(); i++) {
String user;
if (i >= args.length) {
user = "";
} else {
user = args[i];
}
String pm = add ? "+" : "-";
parseMode(pm + action.substring(i, i + 1), user.trim());
}
}
}
private static long parseDateFormat(String s) throws CommandException {
Pattern p = Pattern.compile("((\\d+)y)?((\\d+)d)?((\\d+)h)?((\\d+)m?)?");
Matcher matcher = p.matcher(s);
long time = 0;
if (matcher.matches()) {
String y = matcher.group(2);
String d = matcher.group(4);
String h = matcher.group(6);
String m = matcher.group(8);
try {
if (y != null) time += Integer.parseInt(y) * 31536000;
if (d != null) time += Integer.parseInt(d) * 86400;
if (h != null) time += Integer.parseInt(h) * 3600;
if (m != null) time += Integer.parseInt(m) * 60;
} catch (Exception e) {
throw new CommandException("Time format [_y][_d][_h][_m]");
}
}
return (int)time;
}
public void addMessage(String user, String message) {
addMessage(user, message, true);
}
public void addMessage(String user, String message, boolean encode) {
m_chatPane.addMessage(user, message, encode);
//open new log file if it is a new day
int newTime = CALENDAR.get(Calendar.HOUR);
if (newTime < m_lastTime) {
initLogging();
}
m_lastTime = newTime;
if (m_logging && (m_log != null)) {
if (!encode) message = Text.stripTags(message);
String out = message;
if (user != null) {
user = Text.stripTags(user);
out = user + ": " + message;
}
String timestamp = new SimpleDateFormat("[hh:mm:ss] ").format(new Date());
out = timestamp + out;
m_log.println(out);
}
}
public void sendMessage(String message) throws CommandException {
message = message.trim();
if (message.equals("")) {
return;
}
if (message.indexOf('/') == 0) {
int idx = message.indexOf(' ');
String command, args;
if (idx != -1) {
command = message.substring(1, idx);
args = message.substring(idx + 1);
} else {
command = message.substring(1);
args = "";
}
parseCommand(command.toLowerCase(), args);
return;
}
if (m_channel.getType() == Channel.TYPE_PRIVATE_MESSAGE) {
m_lobby.getLink().sendPrivateMessage(m_channel.getName(), message);
} else {
m_lobby.getLink().sendChannelMessage(m_channel.getId(), message);
}
}
public JScrollPane getPane() {
return scrollChat;
}
public HTMLPane getChat() {
return m_chatPane;
}
@Override
public boolean informClosed() {
if (m_channel.getType() == Channel.TYPE_PRIVATE_MESSAGE) {
m_lobby.closePrivateMessage(m_channel.getName());
} else {
m_lobby.getLink().partChannel(m_channel.getId());
}
return true;
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
txtChat = new javax.swing.JTextField();
scrollChat = new javax.swing.JScrollPane();
setBackground(new java.awt.Color(244, 242, 242));
setOpaque(false);
txtChat.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusGained(java.awt.event.FocusEvent evt) {
txtChatFocusGained(evt);
}
public void focusLost(java.awt.event.FocusEvent evt) {
txtChatFocusLost(evt);
}
});
txtChat.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyReleased(java.awt.event.KeyEvent evt) {
txtChatKeyReleased(evt);
}
});
scrollChat.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
.add(org.jdesktop.layout.GroupLayout.LEADING, txtChat, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 352, Short.MAX_VALUE)
.add(org.jdesktop.layout.GroupLayout.LEADING, scrollChat, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 352, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.add(scrollChat, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 129, Short.MAX_VALUE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(txtChat, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
private void txtChatFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtChatFocusGained
}//GEN-LAST:event_txtChatFocusGained
private void txtChatFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtChatFocusLost
}//GEN-LAST:event_txtChatFocusLost
private void txtChatKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtChatKeyReleased
String msg = txtChat.getText();
if (evt.getKeyCode() == KeyEvent.VK_TAB) {
if ("".equals(msg)) return;
int idx = msg.lastIndexOf(" ");
if (m_tabCount == 0) {
m_sub = msg.substring(idx + 1);
if ("".equals(m_sub)) return;
}
List<String> names = m_lobby.autocompleteUser(m_sub);
Collections.sort(names, new Comparator<String>() {
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
int size = names.size();
if (size == 0) return;
int index = m_tabCount;
while (index >= size) {
index -= size;
}
String newStr = names.get(index);
if (newStr != null){
msg = msg.substring(0, idx + 1) + newStr;
txtChat.setText(msg);
}
m_tabCount++;
} else {
m_tabCount = 0;
if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
try {
sendMessage(msg);
} catch (CommandException e) {
String str = "<font class='help'>" + e.getMessage() + "</font>";
addMessage(null, str, false);
}
txtChat.setText(null);
}
}
}//GEN-LAST:event_txtChatKeyReleased
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane scrollChat;
private javax.swing.JTextField txtChat;
// End of variables declaration//GEN-END:variables
}