001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.gui.inspector; 020 021import icy.gui.component.CloseableTabbedPane; 022import icy.gui.component.CloseableTabbedPane.CloseableTabbedPaneListener; 023import icy.gui.component.ExternalizablePanel; 024import icy.gui.component.IcyTextField; 025import icy.gui.component.button.IcyButton; 026import icy.gui.component.button.IcyToggleButton; 027import icy.gui.frame.progress.AnnounceFrame; 028import icy.gui.frame.progress.ToolTipFrame; 029import icy.gui.main.IcyDesktopPane; 030import icy.gui.main.IcyDesktopPane.AbstractDesktopOverlay; 031import icy.gui.main.MainFrame; 032import icy.gui.preferences.ChatPreferencePanel; 033import icy.gui.preferences.PreferenceFrame; 034import icy.gui.util.ComponentUtil; 035import icy.gui.util.FontUtil; 036import icy.gui.util.GuiUtil; 037import icy.main.Icy; 038import icy.network.IRCClient; 039import icy.network.IRCEventListenerImpl; 040import icy.network.IRCUtil; 041import icy.network.NetworkUtil; 042import icy.network.NetworkUtil.InternetAccessListener; 043import icy.preferences.ChatPreferences; 044import icy.resource.ResourceUtil; 045import icy.resource.icon.IcyIcon; 046import icy.system.IcyExceptionHandler; 047import icy.system.thread.ThreadUtil; 048import icy.type.collection.CollectionUtil; 049import icy.util.DateUtil; 050import icy.util.GraphicsUtil; 051import icy.util.StringUtil; 052 053import java.awt.BorderLayout; 054import java.awt.Color; 055import java.awt.Component; 056import java.awt.Dimension; 057import java.awt.Font; 058import java.awt.Graphics; 059import java.awt.Graphics2D; 060import java.awt.Insets; 061import java.awt.event.ActionEvent; 062import java.awt.event.ActionListener; 063import java.awt.event.KeyAdapter; 064import java.awt.event.KeyEvent; 065import java.awt.event.MouseAdapter; 066import java.awt.event.MouseEvent; 067import java.awt.geom.Rectangle2D; 068import java.io.IOException; 069import java.util.ArrayList; 070 071import javax.swing.Box; 072import javax.swing.BoxLayout; 073import javax.swing.JButton; 074import javax.swing.JLabel; 075import javax.swing.JList; 076import javax.swing.JPanel; 077import javax.swing.JScrollPane; 078import javax.swing.JTabbedPane; 079import javax.swing.JTextField; 080import javax.swing.JTextPane; 081import javax.swing.ListSelectionModel; 082import javax.swing.SwingConstants; 083import javax.swing.border.EmptyBorder; 084import javax.swing.border.LineBorder; 085import javax.swing.event.ChangeEvent; 086import javax.swing.event.ChangeListener; 087import javax.swing.text.BadLocationException; 088import javax.swing.text.SimpleAttributeSet; 089import javax.swing.text.StyleConstants; 090import javax.swing.text.StyledDocument; 091 092import org.schwering.irc.lib.IRCModeParser; 093import org.schwering.irc.lib.IRCUser; 094 095public class ChatPanel extends ExternalizablePanel implements InternetAccessListener 096{ 097 /** 098 * 099 */ 100 private static final long serialVersionUID = -3449422097073285247L; 101 102 private static final int MAX_SIZE = 1 * 1024 * 1024; // 1 MB 103 104 private static final String DEFAULT_CHANNEL = "#icy"; 105 106 private class CustomIRCClient extends IRCClient 107 { 108 public CustomIRCClient(String host, int port, String pass, String nickName, String userName, String realName) 109 { 110 super(host, port, pass, nickName, userName, realName); 111 112 setName("Chat IRC listener"); 113 } 114 } 115 116 private class DesktopOverlay extends AbstractDesktopOverlay 117 { 118 final static int FG_ALPHA = 0xC0; 119 final static int BG_ALPHA = 0xA0; 120 121 final Color defaultBgColor; 122 final Color defaultFgColor; 123 Color bgColor; 124 Color fgColor; 125 126 public DesktopOverlay() 127 { 128 super(); 129 130 // default text colors 131 defaultFgColor = getFgColor(Color.black); 132 defaultBgColor = getBgColor(Color.lightGray); 133 fgColor = defaultFgColor; 134 bgColor = defaultBgColor; 135 } 136 137 private Color getFgColor(Color c) 138 { 139 return new Color(c.getRed(), c.getGreen(), c.getBlue(), FG_ALPHA); 140 } 141 142 private Color getBgColor(Color c) 143 { 144 return new Color(c.getRed(), c.getGreen(), c.getBlue(), BG_ALPHA); 145 } 146 147 private int setAttribute(Graphics2D g2, CharSequence text, int index) 148 { 149 final int len = text.length(); 150 151 // no more text 152 if (index >= len) 153 return len; 154 155 int result = index + 1; 156 int end; 157 Font f; 158 159 switch (text.charAt(index)) 160 { 161 case IRCUtil.CHAR_RESET: 162 // reset to normal 163 g2.setFont(FontUtil.setStyle(g2.getFont(), Font.PLAIN)); 164 fgColor = defaultFgColor; 165 bgColor = defaultBgColor; 166 break; 167 168 case IRCUtil.CHAR_BOLD: 169 // switch bold 170 f = g2.getFont(); 171 g2.setFont(FontUtil.setStyle(f, f.getStyle() ^ Font.BOLD)); 172 break; 173 174 case IRCUtil.CHAR_ITALIC: 175 // switch italic 176 f = g2.getFont(); 177 g2.setFont(FontUtil.setStyle(f, f.getStyle() ^ Font.ITALIC)); 178 break; 179 180 case IRCUtil.CHAR_COLOR: 181 end = StringUtil.getNextNonDigitCharIndex(text, result); 182 // no more than 2 digits to encode color 183 if ((end == -1) || (end > (result + 2))) 184 end = Math.min(text.length(), result + 2); 185 186 // no color info --> restore default 187 if (end == result) 188 { 189 fgColor = defaultFgColor; 190 bgColor = defaultBgColor; 191 } 192 else 193 { 194 // get foreground color 195 fgColor = getFgColor( 196 IRCUtil.getIRCColor(Integer.parseInt(text.subSequence(result, end).toString()))); 197 198 // update position 199 result = end; 200 201 // search if we have background color 202 if ((result < len) && (text.charAt(result) == ',')) 203 { 204 result++; 205 206 end = StringUtil.getNextNonDigitCharIndex(text, result); 207 // no more than 2 digits to encode color 208 if ((end == -1) || (end > (result + 2))) 209 end = Math.min(text.length(), result + 2); 210 211 // get background color 212 if (end != result) 213 { 214 // we don't want to support background color... 215 // bgColor = 216 // getBgColor(IRCUtil.getIRCColor(Integer.parseInt(text.subSequence(result, 217 // end) 218 // .toString()))); 219 220 // update position 221 result = end; 222 } 223 } 224 } 225 break; 226 227 default: 228 // System.out.println("code " + Integer.toString(text.charAt(index))); 229 break; 230 } 231 232 return result; 233 } 234 235 private int firstPreviousIndexOf(char c, int from) 236 { 237 int ind = from; 238 if (ind >= content.length()) 239 return -1; 240 241 while (ind >= 0) 242 { 243 if (content.charAt(ind) == c) 244 return ind; 245 246 ind--; 247 } 248 249 return ind; 250 } 251 252 private int firstNextIndexOf(char c, int from) 253 { 254 final int len = content.length(); 255 int ind = from; 256 257 while (ind < len) 258 { 259 if (content.charAt(ind) == c) 260 return ind; 261 262 ind++; 263 } 264 265 return -1; 266 } 267 268 private boolean isChannelVisible(String channel) 269 { 270 // no channel name --> assume visible 271 if (StringUtil.isEmpty(channel)) 272 return true; 273 // private message --> assume visible 274 if (channel.charAt(0) != '#') 275 return true; 276 277 for (String chan : ChatPreferences.getDesktopChannels().split(";")) 278 if (channel.equalsIgnoreCase(fixChannelName(chan))) 279 return true; 280 281 return false; 282 } 283 284 @Override 285 public void paint(Graphics g, int width, int height) 286 { 287 final Graphics2D g2 = (Graphics2D) g.create(); 288 289 // modify to Monospaced font for easy space calculation 290 g2.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 291 292 // fixed size font, every character has the same bounds 293 final Rectangle2D charRect = GraphicsUtil.getStringBounds(g2, "M"); 294 final float charHeight = (float) charRect.getHeight(); 295 final float charWidth = (float) charRect.getWidth(); 296 // keep 20 pixels margins 297 final int charByLine = (int) ((width - 20) / charRect.getWidth()); 298 299 // no enough space to draw text 300 if (charByLine <= 0) 301 return; 302 303 // start y position 304 float y = height; 305 // start at last character 306 int position = content.length() - 1; 307 308 while ((position > 0) && (y > 0)) 309 { 310 // get paragraph offsets 311 final int start = firstPreviousIndexOf('\n', position - 1) + 1; 312 final int end = position; 313 final int len = end - start; 314 315 // update position 316 position = start - 1; 317 318 // get channel name end position 319 final int chanEnd = firstNextIndexOf(':', start); 320 // display only if this channel is visible on desktop 321 if ((chanEnd != -1) && isChannelVisible(content.substring(start, chanEnd))) 322 { 323 324 // calculate number of lines taken by the paragraph 325 int numLineParagraph = len / charByLine; 326 if ((len % charByLine) != 0) 327 numLineParagraph++; 328 // get paragraph height 329 final float paragraphHeight = numLineParagraph * charHeight; 330 331 // position to end of paragraph 332 y -= paragraphHeight; 333 334 // set default attributes 335 g2.setFont(FontUtil.setStyle(g2.getFont(), Font.PLAIN)); 336 fgColor = defaultFgColor; 337 bgColor = defaultBgColor; 338 339 // process paragraph 340 int index = start; 341 while (index < end) 342 { 343 final int lineEnd = Math.min(index + charByLine, end); 344 float x = 10; 345 346 // process line 347 while (index < lineEnd) 348 { 349 int ctrlIndex = StringUtil.getNextCtrlCharIndex(content, index); 350 // end of line 351 if ((ctrlIndex == -1) || (ctrlIndex > lineEnd)) 352 ctrlIndex = lineEnd; 353 354 // something to draw ? 355 if (index != ctrlIndex) 356 { 357 // get String to draw 358 final String str = content.substring(index, ctrlIndex); 359 360 // draw string 361 g2.setColor(bgColor); 362 g2.drawString(str, x - 1, y + 1); 363 g2.setColor(fgColor); 364 g2.drawString(str, x, y); 365 366 // set new X position 367 x += charWidth * str.length(); 368 } 369 370 if (ctrlIndex < lineEnd) 371 index = setAttribute(g2, content, ctrlIndex); 372 else 373 index = lineEnd; 374 } 375 376 // pass to next line 377 y += charHeight; 378 } 379 380 // set position back to end of paragraph 381 y -= paragraphHeight; 382 } 383 } 384 385 g2.dispose(); 386 } 387 } 388 389 private class CustomIRCClientListener extends IRCEventListenerImpl 390 { 391 public CustomIRCClientListener() 392 { 393 super(); 394 } 395 396 @Override 397 public void onConnected() 398 { 399 super.onConnected(); 400 401 // join default channel 402 client.doJoin(DEFAULT_CHANNEL); 403 // join extras channels 404 for (String extraChannel : ChatPreferences.getExtraChannels().split(";")) 405 if (!StringUtil.isEmpty(extraChannel)) 406 client.doJoin(fixChannelName(extraChannel)); 407 // authentication for registered user 408 final String pass = ChatPreferences.getUserPassword(); 409 if (!StringUtil.isEmpty(pass)) 410 client.doPrivmsg("NickServ", "identify " + ChatPreferences.getNickname() + " " + pass); 411 412 ThreadUtil.invokeLater(new Runnable() 413 { 414 @Override 415 public void run() 416 { 417 connectButton.setEnabled(true); 418 } 419 }); 420 421 refreshGUI(); 422 } 423 424 @Override 425 public void onDisconnected() 426 { 427 super.onDisconnected(); 428 429 ThreadUtil.invokeLater(new Runnable() 430 { 431 @Override 432 public void run() 433 { 434 connectButton.setEnabled(true); 435 } 436 }); 437 438 refreshGUI(); 439 refreshUsers(); 440 } 441 442 @Override 443 public void onError(int num, String msg) 444 { 445 super.onError(num, msg); 446 447 switch (num) 448 { 449 case 432: 450 case 433: 451 // Erroneous Nickname 452 if (!connectButton.isEnabled()) 453 { 454 // if we were connecting, we disconnect 455 disconnect("Incorrect nickname"); 456 if (num == 432) 457 onReceive(null, null, "Your nickname contains invalid caracters."); 458 } 459 refreshGUI(); 460 break; 461 462 case 437: 463 client.doNick(client.getNick() + "_"); 464 break; 465 } 466 } 467 468 @Override 469 public void onJoin(String chan, IRCUser u) 470 { 471 if (isCurrentUser(u)) 472 { 473 // add the channel pane if needed 474 addChannelPane(chan); 475 if (getShowStatusMessages()) 476 onReceive(null, chan, "Welcome to " + IRCUtil.getBoldString(chan.substring(1)) + "."); 477 } 478 else 479 { 480 if (getShowStatusMessages()) 481 onReceive(null, chan, u.getNick() + " joined."); 482 } 483 484 // refresh user list 485 refreshUsers(); 486 } 487 488 @Override 489 public void onKick(String chan, IRCUser u, String nickPass, String msg) 490 { 491 super.onKick(chan, u, nickPass, msg); 492 493 // refresh user list 494 refreshUsers(); 495 } 496 497 @Override 498 public void onLeave(String chan, IRCUser u, String msg) 499 { 500 if (getShowStatusMessages()) 501 { 502 if (StringUtil.isEmpty(msg)) 503 onReceive(null, chan, u.getNick() + " left."); 504 else 505 onReceive(null, chan, u.getNick() + " left" + " (" + msg + ")."); 506 } 507 508 // remove the channel pane if needed 509 if (isCurrentUser(u)) 510 removeChannelPane(chan); 511 512 // refresh user list 513 refreshUsers(); 514 } 515 516 @Override 517 public void onMode(IRCUser u, String nickPass, String mode) 518 { 519 // ignore mode set message 520 // super.onMode(u, nickPass, mode); 521 } 522 523 @Override 524 public void onMode(String chan, IRCUser u, IRCModeParser mp) 525 { 526 // ignore mode set message 527 // super.onMode(chan, u, mp); 528 } 529 530 @Override 531 public void onNick(IRCUser u, String nickNew) 532 { 533 super.onNick(u, nickNew); 534 535 // update nickname 536 if (isCurrentUser(u)) 537 ChatPreferences.setNickname(nickNew); 538 refreshGUI(); 539 refreshUsers(); 540 } 541 542 @Override 543 public void onNotice(String target, IRCUser u, String msg) 544 { 545 if (msg != null) 546 { 547 if (getShowStatusMessages()) 548 { 549 if (msg.indexOf("Looking up your hostname") != -1) 550 onReceive(null, null, "Connecting..."); 551 } 552 553 // ignore all others notices... 554 555 // else 556 // { 557 // if (msg.indexOf("Checking Ident") != -1) 558 // return; 559 // if (msg.indexOf("your hostname") != -1) 560 // return; 561 // if (msg.indexOf("No Ident response") != -1) 562 // return; 563 // 564 // super.onNotice(target, u, msg); 565 // } 566 } 567 } 568 569 @Override 570 public void onQuit(IRCUser u, String msg) 571 { 572 super.onQuit(u, msg); 573 574 refreshUsers(); 575 } 576 577 @Override 578 public void unknown(String a, String b, String c, String d) 579 { 580 onReceive(null, null, d); 581 } 582 583 @Override 584 public void onReply(int num, String value, String msg) 585 { 586 switch (num) 587 { 588 case 353: 589 // add users to tmp list 590 tmpUserList.addAll(CollectionUtil.asList(msg.split(" "))); 591 break; 592 593 case 366: 594 // end of user list 595 userList.setListData(tmpUserList.toArray(new String[tmpUserList.size()])); 596 tmpUserList.clear(); 597 break; 598 599 case 1: 600 case 2: 601 case 3: 602 case 4: 603 case 5: 604 case 250: 605 case 251: 606 case 252: 607 case 253: 608 case 254: 609 case 255: 610 case 256: 611 case 257: 612 case 258: 613 case 259: 614 case 261: 615 case 262: 616 case 263: 617 case 265: 618 case 266: 619 case 372: 620 case 375: 621 case 376: 622 // ignore 623 break; 624 625 default: 626 onReceive(null, null, msg); 627 } 628 } 629 630 @Override 631 public void onReceive(String nick, String target, String msg) 632 { 633 final String n; 634 final String t; 635 636 // ignore close link 637 if (msg.startsWith("Error: Closing Link:")) 638 return; 639 640 // ignore CTCP version request (can come from hacker) 641 if (msg.equals("\001VERSION\001")) 642 return; 643 644 // target is current user --> incoming private message 645 if (isCurrentUser(target)) 646 { 647 // get the source name of private message 648 if (StringUtil.isEmpty(nick)) 649 n = "serv"; 650 else 651 n = nick; 652 653 // incoming private message --> use source as target name 654 t = n; 655 656 // add private channel 657 addChannelPane(n); 658 } 659 else 660 { 661 n = nick; 662 t = target; 663 } 664 665 // show message 666 addMessage(n, t, msg); 667 } 668 669 @Override 670 protected boolean getShowStatusMessages() 671 { 672 return ChatPreferences.getShowStatusMessages(); 673 } 674 } 675 676 private class ChannelPanel 677 { 678 final String name; 679 680 final JScrollPane scrollPane; 681 final JTextPane editor; 682 final StyledDocument doc; 683 684 public ChannelPanel(String name) 685 { 686 super(); 687 688 this.name = name; 689 690 editor = new JTextPane(); 691 editor.setEditable(false); 692 doc = editor.getStyledDocument(); 693 694 scrollPane = new JScrollPane(editor); 695 } 696 } 697 698 /** 699 * GUI 700 */ 701 JPanel panelBottom; 702 CloseableTabbedPane tabPane; 703 JScrollPane usersScrollPane; 704 JList userList; 705 IcyTextField sendEditor; 706 IcyTextField txtNickName; 707 IcyToggleButton connectButton; 708 IcyToggleButton showUserPaneButton; 709 IcyToggleButton desktopOverlayButton; 710 IcyButton advancedButton; 711 712 /** 713 * Desktop GUI 714 */ 715 final JPanel desktopPanel; 716 final IcyTextField sendEditorDesktop; 717 final IcyButton hideDesktopChatButton; 718 final DesktopOverlay desktopOverlay; 719 720 /** 721 * IRC client 722 */ 723 CustomIRCClient client; 724 725 /** 726 * internals 727 */ 728 final ArrayList<ChannelPanel> channelPanes; 729 final ArrayList<String> tmpUserList; 730 final SimpleAttributeSet attributes; 731 final CustomIRCClientListener ircListener; 732 final StringBuilder content; 733 String lastCmd; 734 735 public ChatPanel() 736 { 737 super("Chat room", "chatPanel"); 738 739 channelPanes = new ArrayList<ChannelPanel>(); 740 tmpUserList = new ArrayList<String>(); 741 attributes = new SimpleAttributeSet(); 742 content = new StringBuilder(); 743 lastCmd = ""; 744 client = null; 745 746 // build GUI 747 initialize(); 748 749 // add default channel panel (need to be done when base GUI is done) 750 addChannelPane(DEFAULT_CHANNEL); 751 752 // build desktop GUI 753 sendEditorDesktop = new IcyTextField(); 754 sendEditorDesktop.addActionListener(new ActionListener() 755 { 756 @Override 757 public void actionPerformed(ActionEvent e) 758 { 759 sendEditContent((IcyTextField) e.getSource()); 760 } 761 }); 762 sendEditorDesktop.addKeyListener(new KeyAdapter() 763 { 764 @Override 765 public void keyPressed(KeyEvent e) 766 { 767 if (e.getKeyCode() == KeyEvent.VK_UP) 768 sendEditorDesktop.setText(lastCmd); 769 } 770 }); 771 772 hideDesktopChatButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_SQUARE_DOWN, 20)); 773 hideDesktopChatButton.setFlat(true); 774 ComponentUtil.setFixedWidth(hideDesktopChatButton, 20); 775 hideDesktopChatButton.setToolTipText("Disable desktop chat overlay"); 776 hideDesktopChatButton.addActionListener(new ActionListener() 777 { 778 @Override 779 public void actionPerformed(ActionEvent e) 780 { 781 ChatPreferences.setDesktopOverlay(false); 782 refreshDesktopOverlayState(); 783 new ToolTipFrame( 784 "<b>Desktop chat overlay</b><br><br>" + "You just disabled the desktop chat overlay<br>" 785 + "but you can always access and enable it<br>" + "from the inspector \"Chat\" tab.", 786 "chat.overlay"); 787 } 788 }); 789 790 // desktop bottom panel 791 desktopPanel = GuiUtil.createLineBoxPanel(sendEditorDesktop, Box.createHorizontalStrut(2), 792 Box.createHorizontalGlue(), hideDesktopChatButton); 793 794 desktopOverlay = new DesktopOverlay(); 795 796 StyleConstants.setFontFamily(attributes, "arial"); 797 StyleConstants.setFontSize(attributes, 11); 798 StyleConstants.setForeground(attributes, Color.black); 799 800 sendEditor.setFont(new Font("arial", 0, 11)); 801 sendEditor.addKeyListener(new KeyAdapter() 802 { 803 @Override 804 public void keyPressed(KeyEvent e) 805 { 806 if (e.getKeyCode() == KeyEvent.VK_UP) 807 sendEditor.setText(lastCmd); 808 } 809 }); 810 811 ircListener = new CustomIRCClientListener(); 812 813 refreshGUI(); 814 refreshDesktopOverlayState(); 815 816 addStateListener(new StateListener() 817 { 818 @Override 819 public void stateChanged(ExternalizablePanel source, boolean externalized) 820 { 821 refreshGUI(); 822 } 823 }); 824 825 // call default internet connection callback to process auto connect 826 if (NetworkUtil.hasInternetAccess()) 827 internetUp(); 828 829 NetworkUtil.addInternetAccessListener(this); 830 } 831 832 public boolean isConnected() 833 { 834 return (client != null) && client.isConnected(); 835 } 836 837 /** 838 * Do IRC connection 839 */ 840 public void connect() 841 { 842 if (!isConnected()) 843 { 844 ThreadUtil.invokeLater(new Runnable() 845 { 846 @Override 847 public void run() 848 { 849 // connecting 850 connectButton.setEnabled(false); 851 connectButton.setToolTipText("connecting..."); 852 } 853 }); 854 855 final String nickName = txtNickName.getText(); 856 // apply nickname 857 ChatPreferences.setNickname(nickName); 858 String userName = nickName; 859 String realName = ChatPreferences.getRealname(); 860 861 if (StringUtil.isEmpty(userName)) 862 userName = nickName; 863 if (StringUtil.isEmpty(realName)) 864 realName = nickName; 865 866 // remove listener from previous client 867 if (client != null) 868 client.removeListener(ircListener); 869 870 // we need to recreate the client 871 client = new CustomIRCClient(ChatPreferences.getServer(), ChatPreferences.getPort(), 872 ChatPreferences.getServerPassword(), nickName, userName, realName); 873 client.addListener(ircListener); 874 875 // process connection in a separate thread as it can take sometime 876 new Thread(new Runnable() 877 { 878 @Override 879 public void run() 880 { 881 try 882 { 883 client.connect(); 884 } 885 catch (IOException e) 886 { 887 // error while connecting 888 IcyExceptionHandler.showErrorMessage(e, false, false); 889 System.out.println("Cannot connect to chat."); 890 System.out.println("If you use a proxy, verify you have valid SOCKS settings."); 891 892 // update GUI 893 ThreadUtil.invokeLater(new Runnable() 894 { 895 @Override 896 public void run() 897 { 898 connectButton.setEnabled(true); 899 connectButton.setToolTipText("Not connected - Click to connect"); 900 } 901 }); 902 } 903 } 904 }, "IRC client connection").start(); 905 } 906 } 907 908 public void disconnect(String message) 909 { 910 if (isConnected()) 911 { 912 ThreadUtil.invokeLater(new Runnable() 913 { 914 @Override 915 public void run() 916 { 917 // closing connection 918 connectButton.setEnabled(false); 919 connectButton.setToolTipText("closing connexion..."); 920 } 921 }); 922 923 client.doQuit(message); 924 } 925 } 926 927 protected void sendEditContent(JTextField txtField) 928 { 929 final String text = txtField.getText(); 930 931 if (isConnected() && !StringUtil.isEmpty(text)) 932 { 933 // send from desktop editor ? 934 if (txtField == sendEditorDesktop) 935 // send text to main default channel 936 client.send(DEFAULT_CHANNEL, text); 937 else 938 // send text to current target 939 client.send(getCurrentChannel(), text); 940 } 941 942 txtField.setText(""); 943 lastCmd = text; 944 } 945 946 /** 947 * Build the panel 948 */ 949 private void initialize() 950 { 951 setLayout(new BorderLayout(0, 0)); 952 953 tabPane = new CloseableTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); 954 tabPane.addCloseableTabbedPaneListener(new CloseableTabbedPaneListener() 955 { 956 @Override 957 public void tabClosed(int index, String title) 958 { 959 // it was a channel tab, leave channel 960 if (isConnected() && title.startsWith("#")) 961 client.doPart(title); 962 else 963 // directly remove the channel pane 964 removeChannelPane(title); 965 } 966 967 @Override 968 public boolean tabClosing(int index, String title) 969 { 970 return true; 971 } 972 }); 973 tabPane.addChangeListener(new ChangeListener() 974 { 975 @Override 976 public void stateChanged(ChangeEvent e) 977 { 978 // set back default tab color 979 tabPane.setBackgroundAt(tabPane.getSelectedIndex(), tabPane.getBackground()); 980 refreshUsers(); 981 } 982 }); 983 add(tabPane, BorderLayout.CENTER); 984 985 usersScrollPane = new JScrollPane(); 986 usersScrollPane.setPreferredSize(new Dimension(130, 200)); 987 988 JLabel lblUtilisateur = new JLabel("Users"); 989 lblUtilisateur.setFont(new Font("Tahoma", Font.BOLD, 11)); 990 lblUtilisateur.setBorder(new LineBorder(Color.GRAY, 1, true)); 991 lblUtilisateur.setHorizontalAlignment(SwingConstants.CENTER); 992 usersScrollPane.setColumnHeaderView(lblUtilisateur); 993 994 userList = new JList(); 995 userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 996 userList.addMouseListener(new MouseAdapter() 997 { 998 @Override 999 public void mouseClicked(MouseEvent e) 1000 { 1001 if (!isConnected()) 1002 return; 1003 1004 // double click 1005 if (e.getClickCount() == 2) 1006 { 1007 final int index = userList.locationToIndex(e.getPoint()); 1008 1009 if (index != -1) 1010 { 1011 final String nick = (String) userList.getSelectedValue(); 1012 1013 // add private chat nick pane 1014 if (!isCurrentUser(nick) && !StringUtil.isEmpty(nick)) 1015 addChannelPane(nick); 1016 } 1017 } 1018 } 1019 }); 1020 userList.setToolTipText("Double click on an username to send private message"); 1021 1022 usersScrollPane.setViewportView(userList); 1023 1024 panelBottom = new JPanel(); 1025 add(panelBottom, BorderLayout.SOUTH); 1026 panelBottom.setLayout(new BorderLayout(0, 0)); 1027 1028 sendEditor = new IcyTextField(); 1029 sendEditor.addActionListener(new ActionListener() 1030 { 1031 @Override 1032 public void actionPerformed(ActionEvent e) 1033 { 1034 sendEditContent((IcyTextField) e.getSource()); 1035 } 1036 }); 1037 sendEditor.setColumns(10); 1038 panelBottom.add(sendEditor, BorderLayout.SOUTH); 1039 1040 JPanel topPanel = new JPanel(); 1041 topPanel.setBorder(new EmptyBorder(0, 0, 2, 0)); 1042 add(topPanel, BorderLayout.NORTH); 1043 1044 topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.LINE_AXIS)); 1045 1046 txtNickName = new IcyTextField(); 1047 txtNickName.setMaximumSize(new Dimension(2147483647, 24)); 1048 txtNickName.setText(ChatPreferences.getNickname()); 1049 txtNickName.setToolTipText("Nick name"); 1050 txtNickName.setColumns(10); 1051 topPanel.add(txtNickName); 1052 1053 Component horizontalStrut_3 = Box.createHorizontalStrut(4); 1054 topPanel.add(horizontalStrut_3); 1055 1056 JButton btnSetNickName = new JButton("Set"); 1057 btnSetNickName.setFocusPainted(false); 1058 btnSetNickName.setPreferredSize(new Dimension(40, 23)); 1059 btnSetNickName.setMaximumSize(new Dimension(40, 23)); 1060 btnSetNickName.setMinimumSize(new Dimension(40, 23)); 1061 btnSetNickName.setMargin(new Insets(2, 8, 2, 8)); 1062 btnSetNickName.addActionListener(new ActionListener() 1063 { 1064 @Override 1065 public void actionPerformed(ActionEvent e) 1066 { 1067 final String nickName = txtNickName.getText(); 1068 1069 if (isConnected()) 1070 client.doNick(nickName); 1071 else 1072 ChatPreferences.setNickname(nickName); 1073 } 1074 }); 1075 btnSetNickName.setToolTipText("Set nick name"); 1076 topPanel.add(btnSetNickName); 1077 1078 Component horizontalGlue = Box.createHorizontalGlue(); 1079 topPanel.add(horizontalGlue); 1080 1081 Component horizontalStrut_2 = Box.createHorizontalStrut(10); 1082 topPanel.add(horizontalStrut_2); 1083 1084 connectButton = new IcyToggleButton(new IcyIcon(ResourceUtil.ICON_ON_OFF, 20)); 1085 connectButton.setFocusPainted(false); 1086 connectButton.setMaximumSize(new Dimension(32, 32)); 1087 connectButton.setMinimumSize(new Dimension(24, 24)); 1088 connectButton.setPreferredSize(new Dimension(24, 24)); 1089 connectButton.setToolTipText("Connect"); 1090 connectButton.addActionListener(new ActionListener() 1091 { 1092 @Override 1093 public void actionPerformed(ActionEvent e) 1094 { 1095 if (connectButton.isSelected()) 1096 { 1097 if (!NetworkUtil.hasInternetAccess()) 1098 { 1099 new AnnounceFrame("You need internet connection to connect to the chat.", 10); 1100 connectButton.setSelected(false); 1101 } 1102 else 1103 connect(); 1104 } 1105 else 1106 disconnect("Manual disconnect"); 1107 } 1108 }); 1109 topPanel.add(connectButton); 1110 1111 Component horizontalStrut_1 = Box.createHorizontalStrut(2); 1112 topPanel.add(horizontalStrut_1); 1113 1114 showUserPaneButton = new IcyToggleButton(new IcyIcon("user", 20)); 1115 showUserPaneButton.setFocusPainted(false); 1116 showUserPaneButton.setMaximumSize(new Dimension(32, 32)); 1117 showUserPaneButton.setSelected(ChatPreferences.getShowUsersPanel()); 1118 showUserPaneButton.setMinimumSize(new Dimension(24, 24)); 1119 showUserPaneButton.setPreferredSize(new Dimension(24, 24)); 1120 showUserPaneButton.setToolTipText("Show connected users"); 1121 showUserPaneButton.addActionListener(new ActionListener() 1122 { 1123 @Override 1124 public void actionPerformed(ActionEvent e) 1125 { 1126 final boolean visible = showUserPaneButton.isSelected(); 1127 1128 ChatPreferences.setShowUsersPanel(visible); 1129 refreshGUI(); 1130 } 1131 }); 1132 topPanel.add(showUserPaneButton); 1133 1134 Component horizontalStrut_5 = Box.createHorizontalStrut(2); 1135 topPanel.add(horizontalStrut_5); 1136 1137 desktopOverlayButton = new IcyToggleButton(new IcyIcon(ResourceUtil.ICON_CHAT, 20)); 1138 desktopOverlayButton.setFocusPainted(false); 1139 desktopOverlayButton.setMaximumSize(new Dimension(32, 32)); 1140 desktopOverlayButton.setSelected(ChatPreferences.getDesktopOverlay()); 1141 desktopOverlayButton.setMinimumSize(new Dimension(24, 24)); 1142 desktopOverlayButton.setPreferredSize(new Dimension(24, 24)); 1143 desktopOverlayButton.setToolTipText("Enabled chat on desktop"); 1144 desktopOverlayButton.addActionListener(new ActionListener() 1145 { 1146 @Override 1147 public void actionPerformed(ActionEvent e) 1148 { 1149 final boolean visible = desktopOverlayButton.isSelected(); 1150 1151 ChatPreferences.setDesktopOverlay(visible); 1152 refreshDesktopOverlayState(); 1153 } 1154 }); 1155 topPanel.add(desktopOverlayButton); 1156 1157 Component horizontalStrut_6 = Box.createHorizontalStrut(2); 1158 topPanel.add(horizontalStrut_6); 1159 1160 advancedButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_COG, 20)); 1161 advancedButton.setFocusPainted(false); 1162 advancedButton.setMaximumSize(new Dimension(32, 32)); 1163 advancedButton.setMinimumSize(new Dimension(24, 24)); 1164 advancedButton.setPreferredSize(new Dimension(24, 24)); 1165 advancedButton.setToolTipText("Advanced settings"); 1166 advancedButton.addActionListener(new ActionListener() 1167 { 1168 @Override 1169 public void actionPerformed(ActionEvent e) 1170 { 1171 new PreferenceFrame(ChatPreferencePanel.NODE_NAME); 1172 } 1173 }); 1174 topPanel.add(advancedButton); 1175 } 1176 1177 protected String fixChannelName(String channel) 1178 { 1179 if (!StringUtil.isEmpty(channel) && (channel.charAt(0) != '#')) 1180 return "#" + channel; 1181 1182 return channel; 1183 } 1184 1185 public void addChannelPane(final String channel) 1186 { 1187 // already exists... 1188 if (getChannelPaneIndex(channel) != -1) 1189 return; 1190 1191 ThreadUtil.invokeLater(new Runnable() 1192 { 1193 @Override 1194 public void run() 1195 { 1196 final ChannelPanel channelPane = new ChannelPanel(channel); 1197 1198 // add to list 1199 channelPanes.add(channelPane); 1200 1201 // and add to gui 1202 int index = tabPane.indexOfTab(channel); 1203 // add only if not already present (should alway be the case) 1204 if (index == -1) 1205 { 1206 tabPane.addTab(channel, channelPane.scrollPane); 1207 // get index 1208 index = tabPane.indexOfTab(channel); 1209 // default channel cannot be closed 1210 if (channel.equals(DEFAULT_CHANNEL)) 1211 tabPane.setTabClosable(index, false); 1212 tabPane.setSelectedIndex(index); 1213 } 1214 } 1215 }); 1216 } 1217 1218 public void removeChannelPane(final String channel) 1219 { 1220 final int ind = getChannelPaneIndex(channel); 1221 1222 // channel exists --> remove it 1223 if (ind != -1) 1224 { 1225 ThreadUtil.invokeLater(new Runnable() 1226 { 1227 @Override 1228 public void run() 1229 { 1230 channelPanes.remove(ind); 1231 1232 // remove from tabbed pane if needed 1233 final int indTab = tabPane.indexOfTab(channel); 1234 if (indTab != -1) 1235 tabPane.removeTabAt(indTab); 1236 } 1237 }); 1238 } 1239 } 1240 1241 public int getChannelPaneIndex(String channel) 1242 { 1243 final String c; 1244 1245 // empty channel means default channel 1246 if (StringUtil.isEmpty(channel)) 1247 c = DEFAULT_CHANNEL; 1248 else 1249 c = channel; 1250 1251 for (int i = 0; i < channelPanes.size(); i++) 1252 { 1253 final ChannelPanel cp = channelPanes.get(i); 1254 1255 if (cp.name.equalsIgnoreCase(c)) 1256 return i; 1257 } 1258 1259 return -1; 1260 } 1261 1262 public ChannelPanel getChannelPane(String channel) 1263 { 1264 final int ind = getChannelPaneIndex(channel); 1265 1266 if (ind != -1) 1267 return channelPanes.get(ind); 1268 1269 return null; 1270 } 1271 1272 /** 1273 * Returns the current visible channel (tab channel visible). 1274 */ 1275 public String getCurrentChannel() 1276 { 1277 final int ind = tabPane.getSelectedIndex(); 1278 1279 if (ind != -1) 1280 return tabPane.getTitleAt(ind); 1281 1282 return null; 1283 } 1284 1285 /** 1286 * Mark channel pane with blue color. 1287 */ 1288 protected void markChannelPane(String channel) 1289 { 1290 final int index = getChannelPaneIndex(channel); 1291 1292 if ((index != -1) && (tabPane.getSelectedIndex() != index)) 1293 { 1294 ThreadUtil.invokeLater(new Runnable() 1295 { 1296 @Override 1297 public void run() 1298 { 1299 // change output console tab color when new data 1300 if (index < tabPane.getTabCount()) 1301 tabPane.setBackgroundAt(index, Color.blue); 1302 } 1303 }); 1304 } 1305 } 1306 1307 public JTextPane getChannelEditor(String channel) 1308 { 1309 final ChannelPanel cp = getChannelPane(channel); 1310 1311 if (cp != null) 1312 return cp.editor; 1313 1314 return null; 1315 } 1316 1317 public StyledDocument getChannelDocument(String channel) 1318 { 1319 final ChannelPanel cp = getChannelPane(channel); 1320 1321 if (cp != null) 1322 return cp.doc; 1323 1324 return null; 1325 } 1326 1327 protected boolean isCurrentUser(String nick) 1328 { 1329 return StringUtil.equals(nick, ChatPreferences.getNickname()); 1330 } 1331 1332 protected boolean isCurrentUser(IRCUser u) 1333 { 1334 if (u != null) 1335 return isCurrentUser(u.getNick()); 1336 1337 return false; 1338 } 1339 1340 // int getUsersScrollPaneWidth() 1341 // { 1342 // int result = usersScrollPane.getSize().width; 1343 // if (result == 0) 1344 // return usersScrollPane.getPreferredSize().width; 1345 // return result; 1346 // } 1347 // 1348 // int getCurrentWidth() 1349 // { 1350 // int result = getSize().width; 1351 // if (result == 0) 1352 // return getPreferredSize().width; 1353 // return result; 1354 // } 1355 1356 void addMessage(final String nick, final String target, final String msg) 1357 { 1358 ThreadUtil.invokeLater(new Runnable() 1359 { 1360 @Override 1361 public void run() 1362 { 1363 final String channel; 1364 final String nickStr; 1365 1366 if (StringUtil.isEmpty(target)) 1367 channel = DEFAULT_CHANNEL; 1368 else 1369 channel = target; 1370 1371 if (StringUtil.isEmpty(nick)) 1372 nickStr = ""; 1373 else 1374 nickStr = "<" + nick + "> "; 1375 1376 final String timeStr = DateUtil.now("[HH:mm] "); 1377 1378 synchronized (content) 1379 { 1380 content.append(channel + ": " + timeStr + nickStr + msg + "\n"); 1381 1382 // limit to maximum size 1383 if (content.length() > MAX_SIZE) 1384 content.delete(0, content.length() - MAX_SIZE); 1385 1386 refreshDesktopOverlay(); 1387 } 1388 1389 try 1390 { 1391 final JTextPane editor = getChannelEditor(channel); 1392 final StyledDocument doc = getChannelDocument(channel); 1393 1394 if ((editor != null) && (doc != null)) 1395 { 1396 synchronized (editor) 1397 { 1398 IRCUtil.insertString(timeStr + nickStr + msg + "\n", doc, attributes); 1399 1400 // limit to maximum size 1401 if (doc.getLength() > MAX_SIZE) 1402 doc.remove(0, doc.getLength() - MAX_SIZE); 1403 1404 editor.setCaretPosition(doc.getLength()); 1405 } 1406 } 1407 } 1408 catch (BadLocationException e) 1409 { 1410 e.printStackTrace(); 1411 } 1412 1413 // mark channel pane color 1414 markChannelPane(channel); 1415 } 1416 }); 1417 } 1418 1419 /** 1420 * Refresh GUI state 1421 */ 1422 void refreshGUI() 1423 { 1424 ThreadUtil.invokeLater(new Runnable() 1425 { 1426 @Override 1427 public void run() 1428 { 1429 if (isConnected()) 1430 { 1431 final String nick = client.getNick(); 1432 1433 if (!StringUtil.equals(txtNickName.getText(), nick)) 1434 txtNickName.setText(nick); 1435 1436 sendEditor.setEditable(true); 1437 sendEditorDesktop.setEditable(true); 1438 connectButton.setSelected(true); 1439 connectButton.setToolTipText("Connected - Click to disconnect"); 1440 } 1441 else 1442 { 1443 sendEditor.setEditable(false); 1444 sendEditorDesktop.setEditable(false); 1445 connectButton.setSelected(false); 1446 connectButton.setToolTipText("Not connected - Click to connect"); 1447 } 1448 1449 // user panel visible 1450 remove(usersScrollPane); 1451 panelBottom.remove(usersScrollPane); 1452 if (ChatPreferences.getShowUsersPanel()) 1453 { 1454 if ((getWidth() * 1.5) > getHeight()) 1455 add(usersScrollPane, BorderLayout.EAST); 1456 else 1457 panelBottom.add(usersScrollPane, BorderLayout.CENTER); 1458 } 1459 1460 validate(); 1461 } 1462 }); 1463 } 1464 1465 /** 1466 * Refresh users list 1467 */ 1468 void refreshUsers() 1469 { 1470 if (isConnected()) 1471 { 1472 final String channel = getCurrentChannel(); 1473 1474 if (!StringUtil.isEmpty(channel)) 1475 { 1476 // private channel ? 1477 if (channel.charAt(0) != '#') 1478 // display current user and channel name as users 1479 userList.setListData(new String[] {ChatPreferences.getNickname(), channel}); 1480 else 1481 client.doNames(channel); 1482 } 1483 } 1484 else 1485 userList.setListData(new String[0]); 1486 } 1487 1488 /** 1489 * Refresh desktop overlay state 1490 */ 1491 public void refreshDesktopOverlayState() 1492 { 1493 final MainFrame mainFrame = Icy.getMainInterface().getMainFrame(); 1494 final IcyDesktopPane desktopPane = Icy.getMainInterface().getDesktopPane(); 1495 1496 if ((mainFrame != null) && (desktopPane != null)) 1497 { 1498 final JPanel centerPanel = mainFrame.getCenterPanel(); 1499 1500 // desktop overlay enable ? 1501 if (ChatPreferences.getDesktopOverlay()) 1502 { 1503 // add desktop overlay 1504 desktopPane.addOverlay(desktopOverlay); 1505 centerPanel.add(desktopPanel, BorderLayout.SOUTH); 1506 centerPanel.revalidate(); 1507 } 1508 else 1509 { 1510 // remove desktop overlay 1511 desktopPane.removeOverlay(desktopOverlay); 1512 centerPanel.remove(desktopPanel); 1513 centerPanel.revalidate(); 1514 } 1515 1516 // refresh desktop 1517 desktopPane.repaint(); 1518 } 1519 1520 desktopOverlayButton.setSelected(ChatPreferences.getDesktopOverlay()); 1521 } 1522 1523 /** 1524 * Refresh desktop overlay 1525 */ 1526 void refreshDesktopOverlay() 1527 { 1528 final IcyDesktopPane desktopPane = Icy.getMainInterface().getDesktopPane(); 1529 1530 // refresh desktop overlay 1531 if ((desktopPane != null) && ChatPreferences.getDesktopOverlay()) 1532 desktopPane.repaint(); 1533 } 1534 1535 @Override 1536 public void internetUp() 1537 { 1538 if (!isConnected() && ChatPreferences.getAutoConnect()) 1539 connect(); 1540 } 1541 1542 @Override 1543 public void internetDown() 1544 { 1545 // disconnect("Connection lost"); 1546 } 1547}