package org.limewire.ui.swing.friends.chat; import static org.limewire.ui.swing.util.I18n.tr; import java.awt.Color; import java.awt.Cursor; import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.net.URL; import java.util.Comparator; import java.util.List; import javax.swing.JToolTip; import javax.swing.ListSelectionModel; import javax.swing.table.TableCellEditor; import javax.swing.table.TableColumnModel; import org.jdesktop.application.Resource; import org.limewire.collection.glazedlists.GlazedListsFactory; import org.limewire.friend.api.FriendPresence; import org.limewire.friend.api.Network; import org.limewire.inject.LazySingleton; import org.limewire.ui.swing.table.GlazedJXTable; import org.limewire.ui.swing.table.TablePopupHandler; import org.limewire.ui.swing.util.GuiUtils; import org.limewire.ui.swing.util.I18n; import org.limewire.util.Objects; import ca.odell.glazedlists.FilterList; import ca.odell.glazedlists.GlazedLists; import ca.odell.glazedlists.ObservableElementList; import ca.odell.glazedlists.SortedList; import ca.odell.glazedlists.TextFilterator; import ca.odell.glazedlists.gui.TableFormat; import ca.odell.glazedlists.matchers.TextMatcherEditor; import ca.odell.glazedlists.swing.DefaultEventTableModel; import com.google.inject.Inject; import com.google.inject.Provider; /** * A Table that represents all of the friends that are online. */ @LazySingleton class ChatFriendList extends GlazedJXTable { @Resource private Color backgroundColor; @Resource private Color tooltipBackground; private TablePopupHandler popupHandler; final FilterList<ChatFriend> filter; private final Provider<ChatFrame> chatFrame; @Inject public ChatFriendList(ChatModel chatModel, ConversationPanel conversationPanel, Provider<ChatFrame> chatFrame) { this.chatFrame = chatFrame; GuiUtils.assignResources(this); initialize(); ObservableElementList<ChatFriend> observableList = GlazedListsFactory.observableElementList(chatModel.getChatFriendList(), GlazedLists.beanConnector(ChatFriend.class)); SortedList<ChatFriend> sortedFriends = GlazedListsFactory.sortedList(observableList, new AvailableComparator()); final TextMatcherEditor<ChatFriend> editor = new TextMatcherEditor<ChatFriend>(new FriendFilterator()); filter = GlazedListsFactory.filterList(sortedFriends, editor); setModel(new DefaultEventTableModel<ChatFriend>(filter, new ChatTableFormat())); TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setCellRenderer(new ChatFriendRenderer(conversationPanel)); } private void initialize() { setBackground(backgroundColor); setFillsViewportHeight(true); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); setShowGrid(false, false); setTableHeader(null); setRowHeight(18); } @Inject void register() { CloseListener listener = new CloseListener(); addMouseListener(listener); addMouseMotionListener(listener); addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } @Override public void mousePressed(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger() && popupHandler != null) { int col = columnAtPoint(e.getPoint()); int row = rowAtPoint(e.getPoint()); if (row >= 0 && col >= 0) { getSelectionModel().setSelectionInterval(row, row); popupHandler.maybeShowPopup(e.getComponent(), e.getX(), e.getY()); TableCellEditor editor = getCellEditor(); if (editor != null) { editor.cancelCellEditing(); } } } } }); } @Override public JToolTip createToolTip() { JToolTip tooltip = super.createToolTip(); tooltip.setBackground(tooltipBackground); return tooltip; } /** * Custom tooltip text to display information about the friend. */ @Override public String getToolTipText(MouseEvent event) { Point mousePoint = event.getPoint(); int row = rowAtPoint(mousePoint); if (row == -1) { return null; } ChatFriend chatFriend = filter.get(row); if (chatFriend.isChatting() && isOverClose(mousePoint)) { return tr("Close Conversation"); } StringBuilder tooltip = new StringBuilder(); tooltip.append("<html>") .append("<head>") .append("<style>body { margin: 2px 10px 2px 4px;}</style>") .append("</head>") .append("<body>") .append("<img src=\"") .append(getIconURL(chatFriend.getMode())) .append("\"/> ") .append("<b>").append(chatFriend.getName()); if(chatFriend.getFriend().getNetwork().getType() != Network.Type.FACEBOOK) { tooltip.append(" <").append(chatFriend.getID()) .append(">"); } tooltip.append("</b><br/>"); String status = chatFriend.getStatus(); if (status != null && status.length() > 0) { tooltip.append("<div>").append("<b>" + I18n.tr("Status: ") + "</b>").append(status).append("</div>"); } tooltip.append("</body>") .append("</html>"); return tooltip.toString(); } /** * Returns the friend that is currently selected in the list, * if no friend is selected or the list is empty returns null. */ public ChatFriend getSelectedFriend() { int selectedIndex = getSelectedRow(); if(selectedIndex >= 0 && selectedIndex < getRowCount()) { return filter.get(selectedIndex); } return null; } /** * Sets a popupHandler for displaying a popup menu when * the table is right clicked with the mouse. */ public void setPopupHandler(TablePopupHandler popupHandler) { this.popupHandler = popupHandler; } private static class ChatTableFormat implements TableFormat<ChatFriend> { @Override public int getColumnCount() { return 1; } @Override public String getColumnName(int column) { return ""; } @Override public Object getColumnValue(ChatFriend chatFriend, int column) { return chatFriend; } } /** * Comparator to sort friends in the list. Lists are sorted alphabetically * by those who have conversations started then alphabetically by the state * of the friend presence. */ private class AvailableComparator implements Comparator<ChatFriend> { @Override public int compare(ChatFriend friend1, ChatFriend friend2) { boolean friend1IsChatting = friend1.isChatting() || friend1.hasUnviewedMessages(); boolean friend2IsChatting = friend2.isChatting() || friend2.hasUnviewedMessages(); if(friend1IsChatting && friend2IsChatting) { return friend1.getChatStartTime() < friend2.getChatStartTime() ? -1 : friend1.getChatStartTime() == friend2.getChatStartTime() ? 0 : 1; } else if(friend1IsChatting) { return -1; } else if (friend2IsChatting) { return 1; } int friend1ModeIndex = friend1.getMode().getOrder(); int friend2ModeIndex = friend2.getMode().getOrder(); if (friend1ModeIndex > friend2ModeIndex) { return 1; } else if (friend2ModeIndex > friend1ModeIndex) { return -1; } return Objects.compareToNullIgnoreCase(friend1.getName(), friend2.getName(), false); } } private class FriendFilterator implements TextFilterator<ChatFriend> { @Override public void getFilterStrings(List<String> baseList, ChatFriend element) { baseList.add(element.getName()); } } /** * Returns true if the mouse is over the position where the icon * is displayed. */ private boolean isOverClose(Point mousePosition) { return mousePosition.x > 2 && mousePosition.x < 10; } /** * Listens for mouse actions to close an active chat from within the table. * If a conversation is active, and the mouse is over the icon, converts the * mouse to a hand and will close the chat if clicked. */ private class CloseListener extends MouseAdapter { @Override public void mouseMoved(MouseEvent e) { int row = rowAtPoint(e.getPoint()); if(row >= 0) { ChatFriend friend = filter.get(row); if(friend.isChatting() && isOverClose(e.getPoint())) { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else { setCursor(Cursor.getDefaultCursor()); } } else { setCursor(Cursor.getDefaultCursor()); } } @Override public void mouseClicked(MouseEvent e) { int row = rowAtPoint(e.getPoint()); if(row >= 0) { ChatFriend friend = filter.get(row); if(friend.isChatting() && isOverClose(e.getPoint()) && e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON1) { chatFrame.get().closeConversation(friend); setCursor(Cursor.getDefaultCursor()); } } } // ensure that the cursor is returned to the default when exiting // the table. @Override public void mouseExited(MouseEvent e) { setCursor(Cursor.getDefaultCursor()); } } /** * Returns the URL of an icon for displaying the status icon in the tooltip. */ private String getIconURL(FriendPresence.Mode mode) { switch(mode) { case available: return getURL("/org/limewire/ui/swing/mainframe/resources/icons/friends/available.png"); case dnd: return getURL("/org/limewire/ui/swing/mainframe/resources/icons/friends/doNotDisturb.png"); } return getURL("/org/limewire/ui/swing/mainframe/resources/icons/friends/away.png"); } private String getURL(String path) { URL resource = ChatFriendList.class.getResource(path); return resource != null ? resource.toExternalForm() : ""; } }