diff --git a/Mage.Client/src/main/java/mage/client/chat/ChatPanel.java b/Mage.Client/src/main/java/mage/client/chat/ChatPanel.java index d9f74827da..1e613303b5 100644 --- a/Mage.Client/src/main/java/mage/client/chat/ChatPanel.java +++ b/Mage.Client/src/main/java/mage/client/chat/ChatPanel.java @@ -39,14 +39,19 @@ import java.awt.event.KeyEvent; import java.util.*; import java.util.List; import mage.client.MageFrame; +import mage.client.components.ColorPane; import mage.remote.Session; import mage.view.ChatMessage.MessageColor; +import org.jdesktop.swingx.graphics.ColorUtilities; +import sun.plugin2.gluegen.runtime.CPU; +import javax.swing.*; +import javax.swing.border.EmptyBorder; import javax.swing.table.AbstractTableModel; /** * - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, nantuko */ public class ChatPanel extends javax.swing.JPanel { @@ -56,19 +61,73 @@ public class ChatPanel extends javax.swing.JPanel { private List players = new ArrayList(); private TableModel tableModel; + /** + * Chat message color for opponents. + */ + private static final Color OPPONENT_COLOR = Color.RED; + + /** + * Chat message color for client player. + */ + private static final Color MY_COLOR = Color.GREEN; + + /** + * Chat message color for timestamps. + */ + private static final Color TIMESTAMP_COLOR = new Color(255, 255, 0, 120); + + /** + * Chat message color for messages. + */ + private static final Color MESSAGE_COLOR = Color.white; + + /** + * This will be a chat that will be connected to {this} and will handle redirected messages; + * Mostly used to redirect user messages to another window. + */ + private ChatPanel connectedChat; + + /** + * Parent chat this chat connected to. + * Used to send messages using parent chat as it is the only one connected to server. + */ + private ChatPanel parentChatRef; + + /** + * Selected extended view mode. + */ + private VIEW_MODE extendedViewMode = VIEW_MODE.NONE; + + public enum VIEW_MODE { + NONE, GAME, CHAT + } + + /** + * Maps message colors to {@link Color}. + */ + private static final Map colorMap = new HashMap(); + + static { + colorMap.put(MessageColor.BLACK, Color.black); + colorMap.put(MessageColor.GREEN, Color.green); + colorMap.put(MessageColor.ORANGE, Color.orange); + colorMap.put(MessageColor.BLUE, Color.blue); + colorMap.put(MessageColor.RED, Color.red); + } + /** Creates new form ChatPanel */ public ChatPanel() { this(false); } /** - * @param extendedView if true, adds chat/players tabs + * @param addPlayersTab if true, adds chat/players tabs */ /** Creates new form ChatPanel */ - public ChatPanel(boolean extendedView) { + public ChatPanel(boolean addPlayersTab) { tableModel = new TableModel(); initComponents(); - if (!extendedView) simplifyComponents(); + if (!addPlayersTab) simplifyComponents(); } public void connect(UUID chatId) { @@ -84,26 +143,55 @@ public class ChatPanel extends javax.swing.JPanel { session.leaveChat(chatId); } - public void receiveMessage(String message, MessageColor color) { - switch (color) { - case BLACK: - this.txtConversation.setForeground(Color.BLACK); - break; - case RED: - this.txtConversation.setForeground(Color.RED); - break; - case GREEN: - this.txtConversation.setForeground(Color.GREEN); - break; - case BLUE: - this.txtConversation.setForeground(Color.BLUE); - break; - case ORANGE: - this.txtConversation.setForeground(Color.ORANGE); - break; + /** + * Display message in the chat. + * Use different colors for timestamp, username and message. + * + * @param username message sender + * @param message message itself + * @param time timestamp + * @param color Preferred color. Not used. + */ + public void receiveMessage(String username, String message, String time, MessageColor color) { + if (extendedViewMode.equals(VIEW_MODE.GAME)) { + this.txtConversation.append(TIMESTAMP_COLOR, time + " "); + this.txtConversation.append(MESSAGE_COLOR, (username.isEmpty() ? "" : username + ":") + message + "\n"); + } else { + this.txtConversation.append(TIMESTAMP_COLOR, time + " "); + Color userColor; + if (parentChatRef != null) { + userColor = parentChatRef.session.getUserName().equals(username) ? MY_COLOR : OPPONENT_COLOR; + } else { + userColor = session.getUserName().equals(username) ? MY_COLOR : OPPONENT_COLOR; + } + this.txtConversation.append(userColor, username + ": "); + this.txtConversation.append(MESSAGE_COLOR, message + "\n"); } - this.txtConversation.append(message + "\n"); - txtConversation.setCaretPosition(txtConversation.getText().length() - 1); + } + + public ChatPanel getConnectedChat() { + return connectedChat; + } + + public void setConnectedChat(ChatPanel connectedChat) { + this.connectedChat = connectedChat; + } + + public void setParentChat(ChatPanel parentChatRef) { + this.parentChatRef = parentChatRef; + } + + public void disableInput() { + this.txtMessage.setVisible(false); + } + + public void useExtendedView(VIEW_MODE extendedViewMode) { + this.extendedViewMode = extendedViewMode; + this.txtConversation.setExtBackgroundColor(new Color(0,0,0,100)); + this.txtConversation.setBackground(new Color(0,0,0,0)); + this.txtConversation.setForeground(new Color(255,255,255)); + this.jScrollPane1.setOpaque(false); + this.jScrollPane1.getViewport().setOpaque(false); } class TableModel extends AbstractTableModel { @@ -169,7 +257,9 @@ class TableModel extends AbstractTableModel { txtMessage = new javax.swing.JTextField(); jTabbedPane1 = new javax.swing.JTabbedPane(); jScrollPane1 = new javax.swing.JScrollPane(); - txtConversation = new javax.swing.JTextArea(); + //txtConversation = new JTextArea(); + txtConversation = new ColorPane(); + //txtConversation = new JTextPane(); jScrollPane2 = new javax.swing.JScrollPane(); jTable1 = new javax.swing.JTable(); @@ -181,13 +271,17 @@ class TableModel extends AbstractTableModel { jTabbedPane1.setTabPlacement(javax.swing.JTabbedPane.BOTTOM); - txtConversation.setColumns(20); - txtConversation.setEditable(false); - txtConversation.setFont(new java.awt.Font("Arial", 0, 10)); - txtConversation.setLineWrap(true); - txtConversation.setRows(5); - txtConversation.setWrapStyleWord(true); + //txtConversation.setColumns(20); + txtConversation.setOpaque(false); + //txtConversation.setEditable(false); + txtConversation.setFont(new java.awt.Font("Arial", 0, 14)); + //txtConversation.enableInputMethods(false); + //txtConversation.setLineWrap(true); + //txtConversation.setRows(5); + //txtConversation.setWrapStyleWord(true); + jScrollPane1.setViewportView(txtConversation); + jScrollPane1.setBorder(new EmptyBorder(0,0,0,0)); jTabbedPane1.addTab("chat", jScrollPane1); @@ -238,7 +332,11 @@ class TableModel extends AbstractTableModel { private void txtMessageKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtMessageKeyTyped if (evt.getKeyChar() == KeyEvent.VK_ENTER) { - session.sendChatMessage(chatId, this.txtMessage.getText()); + if (parentChatRef != null) { + parentChatRef.session.sendChatMessage(parentChatRef.chatId, this.txtMessage.getText()); + } else { + session.sendChatMessage(chatId, this.txtMessage.getText()); + } this.txtMessage.setText(""); this.txtMessage.repaint(); } @@ -278,7 +376,8 @@ class TableModel extends AbstractTableModel { private javax.swing.JScrollPane jScrollPane2; private javax.swing.JTabbedPane jTabbedPane1; private javax.swing.JTable jTable1; - private javax.swing.JTextArea txtConversation; + //private javax.swing.JTextArea txtConversation; + private ColorPane txtConversation; private javax.swing.JTextField txtMessage; // End of variables declaration//GEN-END:variables diff --git a/Mage.Client/src/main/java/mage/client/components/ColorPane.java b/Mage.Client/src/main/java/mage/client/components/ColorPane.java new file mode 100644 index 0000000000..52eb9e0b9c --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/components/ColorPane.java @@ -0,0 +1,70 @@ +package mage.client.components; + +import javax.swing.*; +import javax.swing.text.AttributeSet; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyleContext; +import java.awt.*; + +/** + * Enhanced {@link JTextPane} with text highlighting support. + * + * @author nantuko + */ +public class ColorPane extends JTextPane { + + /** + * This method solves the known issue with Nimbus LAF background transparency and background color. + * @param color + */ + public void setExtBackgroundColor(Color color) { + setBackground(new Color(0,0,0,0)); + JPanel jPanel = new JPanel(); + jPanel.setBackground(color); + setLayout(new BorderLayout()); + add(jPanel); + } + + public void append(Color color, String s) { + if (color == null) { + return; + } + + try { + setEditable(true); + + StyleContext sc = StyleContext.getDefaultStyleContext(); + AttributeSet aset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, color); + + int len = getDocument().getLength(); + + setCaretPosition(len); + setCharacterAttributes(aset, false); + replaceSelection(s); + + setEditable(false); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * A little trick to paint black background under the text. + * + * @param g + */ + public void paintChildren(Graphics g) { + super.paintComponent(g); + } + + /** + * A little trick to paint black background under the text. + * + * @param g + */ + public void paintComponent(Graphics g) { + super.paintChildren(g); + } + +} \ No newline at end of file diff --git a/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java b/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java index eacc3035c6..f41ca69784 100644 --- a/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java @@ -67,11 +67,11 @@ public class FeedbackPanel extends javax.swing.JPanel { private Session session; private FeedbackMode mode; - /** Creates new form FeedbackPanel */ - public FeedbackPanel() { - //initComponents(); - customInitComponents(); - } + /** Creates new form FeedbackPanel */ + public FeedbackPanel() { + //initComponents(); + customInitComponents(); + } public void init(UUID gameId) { this.gameId = gameId; @@ -87,60 +87,60 @@ public class FeedbackPanel extends javax.swing.JPanel { case INFORM: this.btnLeft.setVisible(false); this.btnRight.setVisible(false); - this.helper.setState("", false, "", false); + this.helper.setState("", false, "", false); break; case QUESTION: this.btnLeft.setVisible(true); this.btnLeft.setText("Yes"); this.btnRight.setVisible(true); this.btnRight.setText("No"); - this.helper.setState("Yes", true, "Yes", true); + this.helper.setState("Yes", true, "Yes", true); break; case CONFIRM: this.btnLeft.setVisible(true); this.btnLeft.setText("OK"); this.btnRight.setVisible(true); this.btnRight.setText("Cancel"); - this.helper.setState("Ok", true, "Cancel", true); + this.helper.setState("Ok", true, "Cancel", true); break; case CANCEL: this.btnLeft.setVisible(false); this.btnRight.setVisible(true); this.btnRight.setText("Cancel"); - this.helper.setState("", false, "Cancel", true); + this.helper.setState("", false, "Cancel", true); break; case SELECT: this.btnLeft.setVisible(false); this.btnRight.setVisible(true); this.btnRight.setText("Done"); - this.helper.setState("", false, "Done", true); + this.helper.setState("", false, "Done", true); break; } this.btnSpecial.setVisible(special); this.btnSpecial.setText("Special"); - this.helper.setSpecial("Special", special); + this.helper.setSpecial("Special", special); if (message.contains("P}")) { this.btnSpecial.setVisible(true); this.btnSpecial.setText("Pay 2 life"); - this.helper.setSpecial("Pay 2 life", true); + this.helper.setSpecial("Pay 2 life", true); } handleOptions(options); this.revalidate(); this.repaint(); - this.helper.setLinks(btnLeft, btnRight, btnSpecial); + this.helper.setLinks(btnLeft, btnRight, btnSpecial); if (modal) { - this.helper.setVisible(false); - startModal(); - } else { - this.helper.setVisible(true); - } + this.helper.setVisible(false); + startModal(); + } else { + this.helper.setVisible(true); + } } private void handleOptions(Map options) { if (options != null) { if (options.containsKey("UI.right.btn.text")) { this.btnRight.setText((String)options.get("UI.right.btn.text")); - this.helper.setRight((String)options.get("UI.right.btn.text"), true); + this.helper.setRight((String)options.get("UI.right.btn.text"), true); } } } diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index cf0f667e33..c7864044c2 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -50,6 +50,7 @@ import javax.swing.border.LineBorder; import mage.client.MageFrame; import mage.client.cards.Cards; +import mage.client.chat.ChatPanel; import mage.client.dialog.CombatDialog; import mage.client.dialog.ExileZoneDialog; import mage.client.dialog.PickChoiceDialog; @@ -133,7 +134,8 @@ public class GamePanel extends javax.swing.JPanel { components.put("pnlBattlefield", pnlBattlefield); components.put("jPanel3", jPanel3); components.put("hand", hand); - components.put("chatPanel", chatPanel); + components.put("gameChatPanel", gameChatPanel); + components.put("userChatPanel", userChatPanel); components.put("jLayeredPane", jLayeredPane); components.put("gamePanel", this); @@ -141,7 +143,7 @@ public class GamePanel extends javax.swing.JPanel { } public void cleanUp() { - this.chatPanel.disconnect(); + this.gameChatPanel.disconnect(); this.players.clear(); logger.debug("players clear."); this.pnlBattlefield.removeAll(); @@ -166,8 +168,8 @@ public class GamePanel extends javax.swing.JPanel { this.btnConcede.setVisible(true); this.pnlReplay.setVisible(false); this.btnStopWatching.setVisible(false); - this.chatPanel.clear(); - this.chatPanel.connect(session.getGameChatId(gameId)); + this.gameChatPanel.clear(); + this.gameChatPanel.connect(session.getGameChatId(gameId)); if (!session.joinGame(gameId)) hideGame(); } @@ -182,8 +184,8 @@ public class GamePanel extends javax.swing.JPanel { this.btnConcede.setVisible(false); this.btnStopWatching.setVisible(true); this.pnlReplay.setVisible(false); - this.chatPanel.clear(); - this.chatPanel.connect(session.getGameChatId(gameId)); + this.gameChatPanel.clear(); + this.gameChatPanel.connect(session.getGameChatId(gameId)); if (!session.watchGame(gameId)) hideGame(); } @@ -197,7 +199,7 @@ public class GamePanel extends javax.swing.JPanel { this.btnConcede.setVisible(false); this.btnStopWatching.setVisible(false); this.pnlReplay.setVisible(true); - this.chatPanel.clear(); + this.gameChatPanel.clear(); if (!session.startReplay(gameId)) hideGame(); } @@ -493,7 +495,14 @@ public class GamePanel extends javax.swing.JPanel { btnNextPlay = new javax.swing.JButton(); pnlBattlefield = new javax.swing.JPanel(); hand = new mage.client.cards.Cards(true); - chatPanel = new mage.client.chat.ChatPanel(); + gameChatPanel = new mage.client.chat.ChatPanel(); + gameChatPanel.useExtendedView(ChatPanel.VIEW_MODE.GAME); + userChatPanel = new mage.client.chat.ChatPanel(); + userChatPanel.setParentChat(gameChatPanel); + userChatPanel.useExtendedView(ChatPanel.VIEW_MODE.CHAT); + gameChatPanel.setConnectedChat(userChatPanel); + gameChatPanel.disableInput(); + jTabbedPane1 = new JTabbedPane(); hand.setCardDimension(handCardDimension); @@ -502,7 +511,7 @@ public class GamePanel extends javax.swing.JPanel { jSplitPane1.setResizeWeight(1.0); jSplitPane1.setOneTouchExpandable(true); jSplitPane1.setMinimumSize(new java.awt.Dimension(26, 48)); - jSplitPane1.setDividerLocation(Integer.MAX_VALUE); + //jSplitPane1.setDividerLocation(Integer.MAX_VALUE); //pnlGameInfo.setBorder(javax.swing.BorderFactory.createEtchedBorder()); pnlGameInfo.setOpaque(false); @@ -681,6 +690,11 @@ public class GamePanel extends javax.swing.JPanel { hand.setBorder(emptyBorder); HandContainer handContainer = new HandContainer(hand); + jTabbedPane1.setTabPlacement(javax.swing.JTabbedPane.BOTTOM); + jTabbedPane1.addTab("Game", gameChatPanel); + jTabbedPane1.addTab("Chat", userChatPanel); + jTabbedPane1.setSelectedIndex(1); + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); jPanel3.setLayout(jPanel3Layout); jPanel3Layout.setHorizontalGroup( @@ -705,8 +719,8 @@ public class GamePanel extends javax.swing.JPanel { jPanel3.setMinimumSize(new Dimension(1024, 768)); jSplitPane1.setLeftComponent(jPanel3); - chatPanel.setMinimumSize(new java.awt.Dimension(100, 48)); - jSplitPane1.setRightComponent(chatPanel); + gameChatPanel.setMinimumSize(new java.awt.Dimension(100, 48)); + jSplitPane1.setRightComponent(jTabbedPane1); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -804,8 +818,9 @@ public class GamePanel extends javax.swing.JPanel { private javax.swing.JButton btnPreviousPlay; private javax.swing.JButton btnStopReplay; private javax.swing.JButton btnStopWatching; - private mage.client.chat.ChatPanel chatPanel; + private mage.client.chat.ChatPanel gameChatPanel; private mage.client.game.FeedbackPanel feedbackPanel; + private mage.client.chat.ChatPanel userChatPanel; private mage.client.cards.Cards hand; private javax.swing.JPanel jPanel3; private javax.swing.JSplitPane jSplitPane1; @@ -825,5 +840,6 @@ public class GamePanel extends javax.swing.JPanel { private javax.swing.JLabel txtTurn; // End of variables declaration//GEN-END:variables + private JTabbedPane jTabbedPane1; private Border emptyBorder = new EmptyBorder(0,0,0,0); } diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index 61cfa0d159..5c2ec94887 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -102,8 +102,13 @@ public class CallbackClientImpl implements CallbackClient { else if (callback.getMethod().equals("chatMessage")) { ChatMessage message = (ChatMessage) callback.getData(); ChatPanel panel = frame.getChat(callback.getObjectId()); - if (panel != null) - panel.receiveMessage(message.getMessage(), message.getColor()); + if (panel != null) { + if (message.isUserMessage() && panel.getConnectedChat() != null) { + panel.getConnectedChat().receiveMessage(message.getUsername(), message.getMessage(), message.getTime(), ChatMessage.MessageColor.BLACK); + } else { + panel.receiveMessage(message.getUsername(), message.getMessage(), message.getTime(), message.getColor()); + } + } } else if (callback.getMethod().equals("replayInit")) { GamePanel panel = frame.getGame(callback.getObjectId()); diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index a350199d60..e32f9cf419 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -87,6 +87,9 @@ public class TablesPanel extends javax.swing.JPanel { initComponents(); tableTables.createDefaultColumnsFromModel(); + chatPanel.useExtendedView(ChatPanel.VIEW_MODE.NONE); + chatPanel.setOpaque(false); + chatPanel.setBorder(null); Action join = new AbstractAction() { diff --git a/Mage.Common/src/mage/view/ChatMessage.java b/Mage.Common/src/mage/view/ChatMessage.java index 1ca09361ab..63bd30906c 100644 --- a/Mage.Common/src/mage/view/ChatMessage.java +++ b/Mage.Common/src/mage/view/ChatMessage.java @@ -38,6 +38,8 @@ import java.util.UUID; public class ChatMessage implements Serializable { private static final long serialVersionUID = 1L; + private String username; + private String time; private String message; private MessageColor color; @@ -45,8 +47,10 @@ public class ChatMessage implements Serializable { BLACK, RED, GREEN, BLUE, ORANGE; } - public ChatMessage(String message, MessageColor color) { + public ChatMessage(String username, String message, String time, MessageColor color) { + this.username = username; this.message = message; + this.time = time; this.color = color; } @@ -57,4 +61,16 @@ public class ChatMessage implements Serializable { public MessageColor getColor() { return color; } + + public boolean isUserMessage() { + return color != null && color.equals(MessageColor.BLUE); + } + + public String getUsername() { + return username; + } + + public String getTime() { + return time; + } } diff --git a/Mage.Plugins/Mage.Theme.Plugin/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java b/Mage.Plugins/Mage.Theme.Plugin/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java index a6bb182ce8..b57e4aa7f1 100644 --- a/Mage.Plugins/Mage.Theme.Plugin/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java +++ b/Mage.Plugins/Mage.Theme.Plugin/src/main/java/org/mage/plugins/theme/ThemePluginImpl.java @@ -60,7 +60,8 @@ public class ThemePluginImpl implements ThemePlugin { unsetOpaque(ui.get("pnlBattlefield")); unsetOpaque(ui.get("jPanel3")); unsetOpaque(ui.get("hand")); - unsetOpaque(ui.get("chatPanel")); + unsetOpaque(ui.get("gameChatPanel")); + unsetOpaque(ui.get("userChatPanel")); ui.get("gamePanel").remove(ui.get("jLayeredPane")); bgPanel.add(ui.get("jLayeredPane")); diff --git a/Mage.Server/src/main/java/mage/server/ChatSession.java b/Mage.Server/src/main/java/mage/server/ChatSession.java index 964911a8c0..20cddc95cd 100644 --- a/Mage.Server/src/main/java/mage/server/ChatSession.java +++ b/Mage.Server/src/main/java/mage/server/ChatSession.java @@ -75,13 +75,15 @@ public class ChatSession { public void broadcast(String userName, String message, MessageColor color) { Calendar cal = new GregorianCalendar(); - final String msg = timeFormatter.format(cal.getTime()) + " " + userName + ":" + message; + final String msg = message; + final String time = timeFormatter.format(cal.getTime()); + final String username = userName; logger.debug("Broadcasting '" + msg + "' for " + chatId); for (UUID sessionId: clients.keySet()) { Session session = SessionManager.getInstance().getSession(sessionId); if (session != null) try { - session.fireCallback(new ClientCallback("chatMessage", chatId, new ChatMessage(msg, color))); + session.fireCallback(new ClientCallback("chatMessage", chatId, new ChatMessage(username, msg, time, color))); } catch (CallbackException ex) { logger.fatal("broadcast error", ex); }