/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Icy 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.gui.inspector; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.util.ArrayList; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.text.BadLocationException; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import org.schwering.irc.lib.IRCModeParser; import org.schwering.irc.lib.IRCUser; import icy.gui.component.CloseableTabbedPane; import icy.gui.component.CloseableTabbedPane.CloseableTabbedPaneListener; import icy.gui.component.ExternalizablePanel; import icy.gui.component.IcyTextField; import icy.gui.component.button.IcyButton; import icy.gui.component.button.IcyToggleButton; import icy.gui.frame.progress.AnnounceFrame; import icy.gui.frame.progress.ToolTipFrame; import icy.gui.main.IcyDesktopPane; import icy.gui.main.IcyDesktopPane.AbstractDesktopOverlay; import icy.gui.main.MainFrame; import icy.gui.preferences.ChatPreferencePanel; import icy.gui.preferences.PreferenceFrame; import icy.gui.util.ComponentUtil; import icy.gui.util.FontUtil; import icy.gui.util.GuiUtil; import icy.main.Icy; import icy.network.IRCClient; import icy.network.IRCEventListenerImpl; import icy.network.IRCUtil; import icy.network.NetworkUtil; import icy.network.NetworkUtil.InternetAccessListener; import icy.preferences.ChatPreferences; import icy.resource.ResourceUtil; import icy.resource.icon.IcyIcon; import icy.system.IcyExceptionHandler; import icy.system.thread.ThreadUtil; import icy.type.collection.CollectionUtil; import icy.util.DateUtil; import icy.util.GraphicsUtil; import icy.util.StringUtil; public class ChatPanel extends ExternalizablePanel implements InternetAccessListener { /** * */ private static final long serialVersionUID = -3449422097073285247L; private static final int MAX_SIZE = 1 * 1024 * 1024; // 1 MB private static final String DEFAULT_CHANNEL = "#icy"; private class CustomIRCClient extends IRCClient { public CustomIRCClient(String host, int port, String pass, String nickName, String userName, String realName) { super(host, port, pass, nickName, userName, realName); setName("Chat IRC listener"); } } private class DesktopOverlay extends AbstractDesktopOverlay { final static int FG_ALPHA = 0xC0; final static int BG_ALPHA = 0xA0; final Color defaultBgColor; final Color defaultFgColor; Color bgColor; Color fgColor; public DesktopOverlay() { super(); // default text colors defaultFgColor = getFgColor(Color.black); defaultBgColor = getBgColor(Color.lightGray); fgColor = defaultFgColor; bgColor = defaultBgColor; } private Color getFgColor(Color c) { return new Color(c.getRed(), c.getGreen(), c.getBlue(), FG_ALPHA); } private Color getBgColor(Color c) { return new Color(c.getRed(), c.getGreen(), c.getBlue(), BG_ALPHA); } private int setAttribute(Graphics2D g2, CharSequence text, int index) { final int len = text.length(); // no more text if (index >= len) return len; int result = index + 1; int end; Font f; switch (text.charAt(index)) { case IRCUtil.CHAR_RESET: // reset to normal g2.setFont(FontUtil.setStyle(g2.getFont(), Font.PLAIN)); fgColor = defaultFgColor; bgColor = defaultBgColor; break; case IRCUtil.CHAR_BOLD: // switch bold f = g2.getFont(); g2.setFont(FontUtil.setStyle(f, f.getStyle() ^ Font.BOLD)); break; case IRCUtil.CHAR_ITALIC: // switch italic f = g2.getFont(); g2.setFont(FontUtil.setStyle(f, f.getStyle() ^ Font.ITALIC)); break; case IRCUtil.CHAR_COLOR: end = StringUtil.getNextNonDigitCharIndex(text, result); // no more than 2 digits to encode color if ((end == -1) || (end > (result + 2))) end = Math.min(text.length(), result + 2); // no color info --> restore default if (end == result) { fgColor = defaultFgColor; bgColor = defaultBgColor; } else { // get foreground color fgColor = getFgColor( IRCUtil.getIRCColor(Integer.parseInt(text.subSequence(result, end).toString()))); // update position result = end; // search if we have background color if ((result < len) && (text.charAt(result) == ',')) { result++; end = StringUtil.getNextNonDigitCharIndex(text, result); // no more than 2 digits to encode color if ((end == -1) || (end > (result + 2))) end = Math.min(text.length(), result + 2); // get background color if (end != result) { // we don't want to support background color... // bgColor = // getBgColor(IRCUtil.getIRCColor(Integer.parseInt(text.subSequence(result, // end) // .toString()))); // update position result = end; } } } break; default: // System.out.println("code " + Integer.toString(text.charAt(index))); break; } return result; } private int firstPreviousIndexOf(char c, int from) { int ind = from; if (ind >= content.length()) return -1; while (ind >= 0) { if (content.charAt(ind) == c) return ind; ind--; } return ind; } private int firstNextIndexOf(char c, int from) { final int len = content.length(); int ind = from; while (ind < len) { if (content.charAt(ind) == c) return ind; ind++; } return -1; } private boolean isChannelVisible(String channel) { // no channel name --> assume visible if (StringUtil.isEmpty(channel)) return true; // private message --> assume visible if (channel.charAt(0) != '#') return true; for (String chan : ChatPreferences.getDesktopChannels().split(";")) if (channel.equalsIgnoreCase(fixChannelName(chan))) return true; return false; } @Override public void paint(Graphics g, int width, int height) { final Graphics2D g2 = (Graphics2D) g.create(); // modify to Monospaced font for easy space calculation g2.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); // fixed size font, every character has the same bounds final Rectangle2D charRect = GraphicsUtil.getStringBounds(g2, "M"); final float charHeight = (float) charRect.getHeight(); final float charWidth = (float) charRect.getWidth(); // keep 20 pixels margins final int charByLine = (int) ((width - 20) / charRect.getWidth()); // no enough space to draw text if (charByLine <= 0) return; // start y position float y = height; // start at last character int position = content.length() - 1; while ((position > 0) && (y > 0)) { // get paragraph offsets final int start = firstPreviousIndexOf('\n', position - 1) + 1; final int end = position; final int len = end - start; // update position position = start - 1; // get channel name end position final int chanEnd = firstNextIndexOf(':', start); // display only if this channel is visible on desktop if ((chanEnd != -1) && isChannelVisible(content.substring(start, chanEnd))) { // calculate number of lines taken by the paragraph int numLineParagraph = len / charByLine; if ((len % charByLine) != 0) numLineParagraph++; // get paragraph height final float paragraphHeight = numLineParagraph * charHeight; // position to end of paragraph y -= paragraphHeight; // set default attributes g2.setFont(FontUtil.setStyle(g2.getFont(), Font.PLAIN)); fgColor = defaultFgColor; bgColor = defaultBgColor; // process paragraph int index = start; while (index < end) { final int lineEnd = Math.min(index + charByLine, end); float x = 10; // process line while (index < lineEnd) { int ctrlIndex = StringUtil.getNextCtrlCharIndex(content, index); // end of line if ((ctrlIndex == -1) || (ctrlIndex > lineEnd)) ctrlIndex = lineEnd; // something to draw ? if (index != ctrlIndex) { // get String to draw final String str = content.substring(index, ctrlIndex); // draw string g2.setColor(bgColor); g2.drawString(str, x - 1, y + 1); g2.setColor(fgColor); g2.drawString(str, x, y); // set new X position x += charWidth * str.length(); } if (ctrlIndex < lineEnd) index = setAttribute(g2, content, ctrlIndex); else index = lineEnd; } // pass to next line y += charHeight; } // set position back to end of paragraph y -= paragraphHeight; } } g2.dispose(); } } private class CustomIRCClientListener extends IRCEventListenerImpl { public CustomIRCClientListener() { super(); } @Override public void onConnected() { super.onConnected(); // join default channel client.doJoin(DEFAULT_CHANNEL); // join extras channels for (String extraChannel : ChatPreferences.getExtraChannels().split(";")) if (!StringUtil.isEmpty(extraChannel)) client.doJoin(fixChannelName(extraChannel)); // authentication for registered user final String pass = ChatPreferences.getUserPassword(); if (!StringUtil.isEmpty(pass)) client.doPrivmsg("NickServ", "identify " + ChatPreferences.getNickname() + " " + pass); ThreadUtil.invokeLater(new Runnable() { @Override public void run() { connectButton.setEnabled(true); } }); refreshGUI(); } @Override public void onDisconnected() { super.onDisconnected(); ThreadUtil.invokeLater(new Runnable() { @Override public void run() { connectButton.setEnabled(true); } }); refreshGUI(); refreshUsers(); } @Override public void onError(int num, String msg) { super.onError(num, msg); switch (num) { case 432: case 433: // Erroneous Nickname if (!connectButton.isEnabled()) { // if we were connecting, we disconnect disconnect("Incorrect nickname"); if (num == 432) onReceive(null, null, "Your nickname contains invalid caracters."); } refreshGUI(); break; case 437: client.doNick(client.getNick() + "_"); break; } } @Override public void onJoin(String chan, IRCUser u) { if (isCurrentUser(u)) { // add the channel pane if needed addChannelPane(chan); if (getShowStatusMessages()) onReceive(null, chan, "Welcome to " + IRCUtil.getBoldString(chan.substring(1)) + "."); } else { if (getShowStatusMessages()) onReceive(null, chan, u.getNick() + " joined."); } // refresh user list refreshUsers(); } @Override public void onKick(String chan, IRCUser u, String nickPass, String msg) { super.onKick(chan, u, nickPass, msg); // refresh user list refreshUsers(); } @Override public void onLeave(String chan, IRCUser u, String msg) { if (getShowStatusMessages()) { if (StringUtil.isEmpty(msg)) onReceive(null, chan, u.getNick() + " left."); else onReceive(null, chan, u.getNick() + " left" + " (" + msg + ")."); } // remove the channel pane if needed if (isCurrentUser(u)) removeChannelPane(chan); // refresh user list refreshUsers(); } @Override public void onMode(IRCUser u, String nickPass, String mode) { // ignore mode set message // super.onMode(u, nickPass, mode); } @Override public void onMode(String chan, IRCUser u, IRCModeParser mp) { // ignore mode set message // super.onMode(chan, u, mp); } @Override public void onNick(IRCUser u, String nickNew) { super.onNick(u, nickNew); // update nickname if (isCurrentUser(u)) ChatPreferences.setNickname(nickNew); refreshGUI(); refreshUsers(); } @Override public void onNotice(String target, IRCUser u, String msg) { if (msg != null) { if (getShowStatusMessages()) { if (msg.indexOf("Looking up your hostname") != -1) onReceive(null, null, "Connecting..."); } // ignore all others notices... // else // { // if (msg.indexOf("Checking Ident") != -1) // return; // if (msg.indexOf("your hostname") != -1) // return; // if (msg.indexOf("No Ident response") != -1) // return; // // super.onNotice(target, u, msg); // } } } @Override public void onQuit(IRCUser u, String msg) { super.onQuit(u, msg); refreshUsers(); } @Override public void unknown(String a, String b, String c, String d) { onReceive(null, null, d); } @Override public void onReply(int num, String value, String msg) { switch (num) { case 353: // add users to tmp list tmpUserList.addAll(CollectionUtil.asList(msg.split(" "))); break; case 366: // end of user list userList.setListData(tmpUserList.toArray(new String[tmpUserList.size()])); tmpUserList.clear(); break; case 1: case 2: case 3: case 4: case 5: case 250: case 251: case 252: case 253: case 254: case 255: case 256: case 257: case 258: case 259: case 261: case 262: case 263: case 265: case 266: case 372: case 375: case 376: // ignore break; default: onReceive(null, null, msg); } } @Override public void onReceive(String nick, String target, String msg) { final String n; final String t; // ignore close link if (msg.startsWith("Error: Closing Link:")) return; // ignore CTCP version request (can come from hacker) if (msg.equals("\001VERSION\001")) return; // target is current user --> incoming private message if (isCurrentUser(target)) { // get the source name of private message if (StringUtil.isEmpty(nick)) n = "serv"; else n = nick; // incoming private message --> use source as target name t = n; // add private channel addChannelPane(n); } else { n = nick; t = target; } // show message addMessage(n, t, msg); } @Override protected boolean getShowStatusMessages() { return ChatPreferences.getShowStatusMessages(); } } private class ChannelPanel { final String name; final JScrollPane scrollPane; final JTextPane editor; final StyledDocument doc; public ChannelPanel(String name) { super(); this.name = name; editor = new JTextPane(); editor.setEditable(false); doc = editor.getStyledDocument(); scrollPane = new JScrollPane(editor); } } /** * GUI */ JPanel panelBottom; CloseableTabbedPane tabPane; JScrollPane usersScrollPane; JList userList; IcyTextField sendEditor; IcyTextField txtNickName; IcyToggleButton connectButton; IcyToggleButton showUserPaneButton; IcyToggleButton desktopOverlayButton; IcyButton advancedButton; /** * Desktop GUI */ final JPanel desktopPanel; final IcyTextField sendEditorDesktop; final IcyButton hideDesktopChatButton; final DesktopOverlay desktopOverlay; /** * IRC client */ CustomIRCClient client; /** * internals */ final ArrayList<ChannelPanel> channelPanes; final ArrayList<String> tmpUserList; final SimpleAttributeSet attributes; final CustomIRCClientListener ircListener; final StringBuilder content; String lastCmd; public ChatPanel() { super("Chat room", "chatPanel"); channelPanes = new ArrayList<ChannelPanel>(); tmpUserList = new ArrayList<String>(); attributes = new SimpleAttributeSet(); content = new StringBuilder(); lastCmd = ""; client = null; // build GUI initialize(); // add default channel panel (need to be done when base GUI is done) addChannelPane(DEFAULT_CHANNEL); // build desktop GUI sendEditorDesktop = new IcyTextField(); sendEditorDesktop.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { sendEditContent((IcyTextField) e.getSource()); } }); sendEditorDesktop.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_UP) sendEditorDesktop.setText(lastCmd); } }); hideDesktopChatButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_SQUARE_DOWN, 20)); hideDesktopChatButton.setFlat(true); ComponentUtil.setFixedWidth(hideDesktopChatButton, 20); hideDesktopChatButton.setToolTipText("Disable desktop chat overlay"); hideDesktopChatButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ChatPreferences.setDesktopOverlay(false); refreshDesktopOverlayState(); new ToolTipFrame( "<b>Desktop chat overlay</b><br><br>" + "You just disabled the desktop chat overlay<br>" + "but you can always access and enable it<br>" + "from the inspector \"Chat\" tab.", "chat.overlay"); } }); // desktop bottom panel desktopPanel = GuiUtil.createLineBoxPanel(sendEditorDesktop, Box.createHorizontalStrut(2), Box.createHorizontalGlue(), hideDesktopChatButton); desktopOverlay = new DesktopOverlay(); StyleConstants.setFontFamily(attributes, "arial"); StyleConstants.setFontSize(attributes, 11); StyleConstants.setForeground(attributes, Color.black); sendEditor.setFont(new Font("arial", 0, 11)); sendEditor.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_UP) sendEditor.setText(lastCmd); } }); ircListener = new CustomIRCClientListener(); refreshGUI(); refreshDesktopOverlayState(); addStateListener(new StateListener() { @Override public void stateChanged(ExternalizablePanel source, boolean externalized) { refreshGUI(); } }); // call default internet connection callback to process auto connect if (NetworkUtil.hasInternetAccess()) internetUp(); NetworkUtil.addInternetAccessListener(this); } public boolean isConnected() { return (client != null) && client.isConnected(); } /** * Do IRC connection */ public void connect() { if (!isConnected()) { ThreadUtil.invokeLater(new Runnable() { @Override public void run() { // connecting connectButton.setEnabled(false); connectButton.setToolTipText("connecting..."); } }); final String nickName = txtNickName.getText(); // apply nickname ChatPreferences.setNickname(nickName); String userName = nickName; String realName = ChatPreferences.getRealname(); if (StringUtil.isEmpty(userName)) userName = nickName; if (StringUtil.isEmpty(realName)) realName = nickName; // remove listener from previous client if (client != null) client.removeListener(ircListener); // we need to recreate the client client = new CustomIRCClient(ChatPreferences.getServer(), ChatPreferences.getPort(), ChatPreferences.getServerPassword(), nickName, userName, realName); client.addListener(ircListener); // process connection in a separate thread as it can take sometime new Thread(new Runnable() { @Override public void run() { try { client.connect(); } catch (IOException e) { // error while connecting IcyExceptionHandler.showErrorMessage(e, false, false); System.out.println("Cannot connect to chat."); System.out.println("If you use a proxy, verify you have valid SOCKS settings."); // update GUI ThreadUtil.invokeLater(new Runnable() { @Override public void run() { connectButton.setEnabled(true); connectButton.setToolTipText("Not connected - Click to connect"); } }); } } }, "IRC client connection").start(); } } public void disconnect(String message) { if (isConnected()) { ThreadUtil.invokeLater(new Runnable() { @Override public void run() { // closing connection connectButton.setEnabled(false); connectButton.setToolTipText("closing connexion..."); } }); client.doQuit(message); } } protected void sendEditContent(JTextField txtField) { final String text = txtField.getText(); if (isConnected() && !StringUtil.isEmpty(text)) { // send from desktop editor ? if (txtField == sendEditorDesktop) // send text to main default channel client.send(DEFAULT_CHANNEL, text); else // send text to current target client.send(getCurrentChannel(), text); } txtField.setText(""); lastCmd = text; } /** * Build the panel */ private void initialize() { setLayout(new BorderLayout(0, 0)); tabPane = new CloseableTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); tabPane.addCloseableTabbedPaneListener(new CloseableTabbedPaneListener() { @Override public void tabClosed(int index, String title) { // it was a channel tab, leave channel if (isConnected() && title.startsWith("#")) client.doPart(title); else // directly remove the channel pane removeChannelPane(title); } @Override public boolean tabClosing(int index, String title) { return true; } }); tabPane.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { // set back default tab color tabPane.setBackgroundAt(tabPane.getSelectedIndex(), tabPane.getBackground()); refreshUsers(); } }); add(tabPane, BorderLayout.CENTER); usersScrollPane = new JScrollPane(); usersScrollPane.setPreferredSize(new Dimension(130, 200)); JLabel lblUtilisateur = new JLabel("Users"); lblUtilisateur.setFont(new Font("Tahoma", Font.BOLD, 11)); lblUtilisateur.setBorder(new LineBorder(Color.GRAY, 1, true)); lblUtilisateur.setHorizontalAlignment(SwingConstants.CENTER); usersScrollPane.setColumnHeaderView(lblUtilisateur); userList = new JList(); userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); userList.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (!isConnected()) return; // double click if (e.getClickCount() == 2) { final int index = userList.locationToIndex(e.getPoint()); if (index != -1) { final String nick = (String) userList.getSelectedValue(); // add private chat nick pane if (!isCurrentUser(nick) && !StringUtil.isEmpty(nick)) addChannelPane(nick); } } } }); userList.setToolTipText("Double click on an username to send private message"); usersScrollPane.setViewportView(userList); panelBottom = new JPanel(); add(panelBottom, BorderLayout.SOUTH); panelBottom.setLayout(new BorderLayout(0, 0)); sendEditor = new IcyTextField(); sendEditor.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { sendEditContent((IcyTextField) e.getSource()); } }); sendEditor.setColumns(10); panelBottom.add(sendEditor, BorderLayout.SOUTH); JPanel topPanel = new JPanel(); topPanel.setBorder(new EmptyBorder(0, 0, 2, 0)); add(topPanel, BorderLayout.NORTH); topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.LINE_AXIS)); txtNickName = new IcyTextField(); txtNickName.setMaximumSize(new Dimension(2147483647, 24)); txtNickName.setText(ChatPreferences.getNickname()); txtNickName.setToolTipText("Nick name"); txtNickName.setColumns(10); topPanel.add(txtNickName); Component horizontalStrut_3 = Box.createHorizontalStrut(4); topPanel.add(horizontalStrut_3); JButton btnSetNickName = new JButton("Set"); btnSetNickName.setFocusPainted(false); btnSetNickName.setPreferredSize(new Dimension(40, 23)); btnSetNickName.setMaximumSize(new Dimension(40, 23)); btnSetNickName.setMinimumSize(new Dimension(40, 23)); btnSetNickName.setMargin(new Insets(2, 8, 2, 8)); btnSetNickName.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final String nickName = txtNickName.getText(); if (isConnected()) client.doNick(nickName); else ChatPreferences.setNickname(nickName); } }); btnSetNickName.setToolTipText("Set nick name"); topPanel.add(btnSetNickName); Component horizontalGlue = Box.createHorizontalGlue(); topPanel.add(horizontalGlue); Component horizontalStrut_2 = Box.createHorizontalStrut(10); topPanel.add(horizontalStrut_2); connectButton = new IcyToggleButton(new IcyIcon(ResourceUtil.ICON_ON_OFF, 20)); connectButton.setFocusPainted(false); connectButton.setMaximumSize(new Dimension(32, 32)); connectButton.setMinimumSize(new Dimension(24, 24)); connectButton.setPreferredSize(new Dimension(24, 24)); connectButton.setToolTipText("Connect"); connectButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (connectButton.isSelected()) { if (!NetworkUtil.hasInternetAccess()) { new AnnounceFrame("You need internet connection to connect to the chat.", 10); connectButton.setSelected(false); } else connect(); } else disconnect("Manual disconnect"); } }); topPanel.add(connectButton); Component horizontalStrut_1 = Box.createHorizontalStrut(2); topPanel.add(horizontalStrut_1); showUserPaneButton = new IcyToggleButton(new IcyIcon("user", 20)); showUserPaneButton.setFocusPainted(false); showUserPaneButton.setMaximumSize(new Dimension(32, 32)); showUserPaneButton.setSelected(ChatPreferences.getShowUsersPanel()); showUserPaneButton.setMinimumSize(new Dimension(24, 24)); showUserPaneButton.setPreferredSize(new Dimension(24, 24)); showUserPaneButton.setToolTipText("Show connected users"); showUserPaneButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final boolean visible = showUserPaneButton.isSelected(); ChatPreferences.setShowUsersPanel(visible); refreshGUI(); } }); topPanel.add(showUserPaneButton); Component horizontalStrut_5 = Box.createHorizontalStrut(2); topPanel.add(horizontalStrut_5); desktopOverlayButton = new IcyToggleButton(new IcyIcon(ResourceUtil.ICON_CHAT, 20)); desktopOverlayButton.setFocusPainted(false); desktopOverlayButton.setMaximumSize(new Dimension(32, 32)); desktopOverlayButton.setSelected(ChatPreferences.getDesktopOverlay()); desktopOverlayButton.setMinimumSize(new Dimension(24, 24)); desktopOverlayButton.setPreferredSize(new Dimension(24, 24)); desktopOverlayButton.setToolTipText("Enabled chat on desktop"); desktopOverlayButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final boolean visible = desktopOverlayButton.isSelected(); ChatPreferences.setDesktopOverlay(visible); refreshDesktopOverlayState(); } }); topPanel.add(desktopOverlayButton); Component horizontalStrut_6 = Box.createHorizontalStrut(2); topPanel.add(horizontalStrut_6); advancedButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_COG, 20)); advancedButton.setFocusPainted(false); advancedButton.setMaximumSize(new Dimension(32, 32)); advancedButton.setMinimumSize(new Dimension(24, 24)); advancedButton.setPreferredSize(new Dimension(24, 24)); advancedButton.setToolTipText("Advanced settings"); advancedButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new PreferenceFrame(ChatPreferencePanel.NODE_NAME); } }); topPanel.add(advancedButton); } protected String fixChannelName(String channel) { if (!StringUtil.isEmpty(channel) && (channel.charAt(0) != '#')) return "#" + channel; return channel; } public void addChannelPane(final String channel) { // already exists... if (getChannelPaneIndex(channel) != -1) return; ThreadUtil.invokeLater(new Runnable() { @Override public void run() { final ChannelPanel channelPane = new ChannelPanel(channel); // add to list channelPanes.add(channelPane); // and add to gui int index = tabPane.indexOfTab(channel); // add only if not already present (should alway be the case) if (index == -1) { tabPane.addTab(channel, channelPane.scrollPane); // get index index = tabPane.indexOfTab(channel); // default channel cannot be closed if (channel.equals(DEFAULT_CHANNEL)) tabPane.setTabClosable(index, false); tabPane.setSelectedIndex(index); } } }); } public void removeChannelPane(final String channel) { final int ind = getChannelPaneIndex(channel); // channel exists --> remove it if (ind != -1) { ThreadUtil.invokeLater(new Runnable() { @Override public void run() { channelPanes.remove(ind); // remove from tabbed pane if needed final int indTab = tabPane.indexOfTab(channel); if (indTab != -1) tabPane.removeTabAt(indTab); } }); } } public int getChannelPaneIndex(String channel) { final String c; // empty channel means default channel if (StringUtil.isEmpty(channel)) c = DEFAULT_CHANNEL; else c = channel; for (int i = 0; i < channelPanes.size(); i++) { final ChannelPanel cp = channelPanes.get(i); if (cp.name.equalsIgnoreCase(c)) return i; } return -1; } public ChannelPanel getChannelPane(String channel) { final int ind = getChannelPaneIndex(channel); if (ind != -1) return channelPanes.get(ind); return null; } /** * Returns the current visible channel (tab channel visible). */ public String getCurrentChannel() { final int ind = tabPane.getSelectedIndex(); if (ind != -1) return tabPane.getTitleAt(ind); return null; } /** * Mark channel pane with blue color. */ protected void markChannelPane(String channel) { final int index = getChannelPaneIndex(channel); if ((index != -1) && (tabPane.getSelectedIndex() != index)) { ThreadUtil.invokeLater(new Runnable() { @Override public void run() { // change output console tab color when new data if (index < tabPane.getTabCount()) tabPane.setBackgroundAt(index, Color.blue); } }); } } public JTextPane getChannelEditor(String channel) { final ChannelPanel cp = getChannelPane(channel); if (cp != null) return cp.editor; return null; } public StyledDocument getChannelDocument(String channel) { final ChannelPanel cp = getChannelPane(channel); if (cp != null) return cp.doc; return null; } protected boolean isCurrentUser(String nick) { return StringUtil.equals(nick, ChatPreferences.getNickname()); } protected boolean isCurrentUser(IRCUser u) { if (u != null) return isCurrentUser(u.getNick()); return false; } // int getUsersScrollPaneWidth() // { // int result = usersScrollPane.getSize().width; // if (result == 0) // return usersScrollPane.getPreferredSize().width; // return result; // } // // int getCurrentWidth() // { // int result = getSize().width; // if (result == 0) // return getPreferredSize().width; // return result; // } void addMessage(final String nick, final String target, final String msg) { ThreadUtil.invokeLater(new Runnable() { @Override public void run() { final String channel; final String nickStr; if (StringUtil.isEmpty(target)) channel = DEFAULT_CHANNEL; else channel = target; if (StringUtil.isEmpty(nick)) nickStr = ""; else nickStr = "<" + nick + "> "; final String timeStr = DateUtil.now("[HH:mm] "); synchronized (content) { content.append(channel + ": " + timeStr + nickStr + msg + "\n"); // limit to maximum size if (content.length() > MAX_SIZE) content.delete(0, content.length() - MAX_SIZE); refreshDesktopOverlay(); } try { final JTextPane editor = getChannelEditor(channel); final StyledDocument doc = getChannelDocument(channel); if ((editor != null) && (doc != null)) { synchronized (editor) { IRCUtil.insertString(timeStr + nickStr + msg + "\n", doc, attributes); // limit to maximum size if (doc.getLength() > MAX_SIZE) doc.remove(0, doc.getLength() - MAX_SIZE); editor.setCaretPosition(doc.getLength()); } } } catch (BadLocationException e) { e.printStackTrace(); } // mark channel pane color markChannelPane(channel); } }); } /** * Refresh GUI state */ void refreshGUI() { ThreadUtil.invokeLater(new Runnable() { @Override public void run() { if (isConnected()) { final String nick = client.getNick(); if (!StringUtil.equals(txtNickName.getText(), nick)) txtNickName.setText(nick); sendEditor.setEditable(true); sendEditorDesktop.setEditable(true); connectButton.setSelected(true); connectButton.setToolTipText("Connected - Click to disconnect"); } else { sendEditor.setEditable(false); sendEditorDesktop.setEditable(false); connectButton.setSelected(false); connectButton.setToolTipText("Not connected - Click to connect"); } // user panel visible remove(usersScrollPane); panelBottom.remove(usersScrollPane); if (ChatPreferences.getShowUsersPanel()) { if ((getWidth() * 1.5) > getHeight()) add(usersScrollPane, BorderLayout.EAST); else panelBottom.add(usersScrollPane, BorderLayout.CENTER); } validate(); } }); } /** * Refresh users list */ void refreshUsers() { if (isConnected()) { final String channel = getCurrentChannel(); if (!StringUtil.isEmpty(channel)) { // private channel ? if (channel.charAt(0) != '#') // display current user and channel name as users userList.setListData(new String[] {ChatPreferences.getNickname(), channel}); else client.doNames(channel); } } else userList.setListData(new String[0]); } /** * Refresh desktop overlay state */ public void refreshDesktopOverlayState() { final MainFrame mainFrame = Icy.getMainInterface().getMainFrame(); final IcyDesktopPane desktopPane = Icy.getMainInterface().getDesktopPane(); if ((mainFrame != null) && (desktopPane != null)) { final JPanel centerPanel = mainFrame.getCenterPanel(); // desktop overlay enable ? if (ChatPreferences.getDesktopOverlay()) { // add desktop overlay desktopPane.addOverlay(desktopOverlay); centerPanel.add(desktopPanel, BorderLayout.SOUTH); centerPanel.revalidate(); } else { // remove desktop overlay desktopPane.removeOverlay(desktopOverlay); centerPanel.remove(desktopPanel); centerPanel.revalidate(); } // refresh desktop desktopPane.repaint(); } desktopOverlayButton.setSelected(ChatPreferences.getDesktopOverlay()); } /** * Refresh desktop overlay */ void refreshDesktopOverlay() { final IcyDesktopPane desktopPane = Icy.getMainInterface().getDesktopPane(); // refresh desktop overlay if ((desktopPane != null) && ChatPreferences.getDesktopOverlay()) desktopPane.repaint(); } @Override public void internetUp() { if (!isConnected() && ChatPreferences.getAutoConnect()) connect(); } @Override public void internetDown() { // disconnect("Connection lost"); } }