/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package net.java.sip.communicator.impl.gui.main.contactlist; import java.awt.*; import java.util.*; import javax.swing.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.skin.*; import net.java.sip.communicator.util.swing.*; /** * The ContactListCellRenderer is the custom cell renderer used in the * Jitsi's ContactList. It extends JPanel instead of JLabel, * which allows adding different buttons and icons to the contact cell. The cell * border and background are repainted. * * @author Yana Stamcheva * @author Lubomir Marinov * @author Adam Netocny */ public class ContactListCellRenderer extends JPanel implements ListCellRenderer, Icon, Skinnable { /** * Serial version UID. */ private static final long serialVersionUID = 0L; /** * The avatar icon height. */ private static final int AVATAR_HEIGHT = 30; /** * The avatar icon width. */ private static final int AVATAR_WIDTH = 30; /** * The key of the user data in MetaContact which specifies * the avatar cached from previous invocations. */ private static final String AVATAR_DATA_KEY = ContactListCellRenderer.class.getName() + ".avatar"; /** * The icon indicating an open group. */ private ImageIcon openedGroupIcon = new ImageIcon(ImageLoader.getImage(ImageLoader.DOWN_ARROW_ICON)); /** * The icon indicating a closed group. */ private ImageIcon closedGroupIcon = new ImageIcon(ImageLoader.getImage(ImageLoader.RIGHT_ARROW_ICON)); /** * The foreground color for groups. */ private Color groupForegroundColor; /** * The foreground color for contacts. */ protected Color contactForegroundColor; /** * The component showing the name of the contact or group. */ protected final JLabel nameLabel = new JLabel(); /** * The status message label. */ protected final JLabel statusMessageLabel = new JLabel(); /** * The component showing the avatar or the contact count in the case of * groups. */ protected final JLabel rightLabel = new JLabel(); /** * An icon indicating that a new message has been received from the * corresponding contact. */ private final Image msgReceivedImage = ImageLoader.getImage(ImageLoader.MESSAGE_RECEIVED_ICON); /** * The label containing the status icon. */ private final JLabel statusLabel = new JLabel(); /** * The icon showing the contact status. */ protected final ImageIcon statusIcon = new ImageIcon(); /** * The panel containing the name and status message labels. */ private final TransparentPanel centerPanel = new TransparentPanel(new GridLayout(0, 1)); /** * Indicates if the current list cell is selected. */ protected boolean isSelected = false; /** * The index of the current cell. */ protected int index = 0; /** * Indicates if the current cell contains a leaf or a group. */ protected boolean isLeaf = true; /** * Initializes the panel containing the node. */ public ContactListCellRenderer() { super(new BorderLayout()); int groupForegroundProperty = GuiActivator.getResources() .getColor("service.gui.CONTACT_LIST_GROUP_FOREGROUND"); if (groupForegroundProperty > -1) groupForegroundColor = new Color (groupForegroundProperty); int contactForegroundProperty = GuiActivator.getResources() .getColor("service.gui.CONTACT_LIST_CONTACT_FOREGROUND"); if (contactForegroundProperty > -1) contactForegroundColor = new Color(contactForegroundProperty); this.setOpaque(false); this.nameLabel.setOpaque(false); this.nameLabel.setPreferredSize(new Dimension(10, 20)); this.statusMessageLabel.setFont(getFont().deriveFont(9f)); this.statusMessageLabel.setForeground(Color.GRAY); this.rightLabel.setFont(rightLabel.getFont().deriveFont(9f)); this.rightLabel.setHorizontalAlignment(JLabel.RIGHT); centerPanel.add(nameLabel); statusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2)); this.add(statusLabel, BorderLayout.WEST); this.add(centerPanel, BorderLayout.CENTER); this.add(rightLabel, BorderLayout.EAST); this.setToolTipText(""); } /** * Implements the ListCellRenderer method. Returns this panel that * has been configured to display the meta contact and meta contact group * cells. * * @param list the source list * @param value the value of the current cell * @param index the index of the current cell in the source list * @param isSelected indicates if this cell is selected * @param cellHasFocus indicates if this cell is focused * * @return this panel */ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { this.index = index; this.rightLabel.setIcon(null); DefaultContactList contactList = (DefaultContactList) list; if (value instanceof MetaContact) { this.setPreferredSize(new Dimension(20, 30)); MetaContact metaContact = (MetaContact) value; String displayName = metaContact.getDisplayName(); if (displayName == null || displayName.length() < 1) { displayName = GuiActivator.getResources() .getI18NString("service.gui.UNKNOWN"); } this.nameLabel.setText(displayName); if(contactList.isMetaContactActive(metaContact)) { statusIcon.setImage(msgReceivedImage); } else { statusIcon.setImage(Constants.getStatusIcon( contactList.getMetaContactStatus(metaContact))); } this.statusLabel.setIcon(statusIcon); this.nameLabel.setFont(this.getFont().deriveFont(Font.PLAIN)); if (contactForegroundColor != null) this.nameLabel.setForeground(contactForegroundColor); String statusMessage = getStatusMessage(metaContact); if (getStatusMessage(metaContact) != null) { statusMessageLabel.setText(statusMessage); centerPanel.add(statusMessageLabel); } else centerPanel.remove(statusMessageLabel); this.setBorder(BorderFactory.createEmptyBorder(1, 3, 1, 3)); ImageIcon avatar = getAvatar(metaContact); if (avatar != null) this.rightLabel.setIcon(avatar); this.rightLabel.setText(""); // We should set the bounds of the cell explicitly in order to // make getComponentAt work properly. final int listWidth = list.getWidth(); this.setBounds(0, 0, listWidth - 2, 30); this.nameLabel.setBounds(0, 0, listWidth - 28, 17); this.rightLabel.setBounds(listWidth - 28, 0, 25, 30); this.isLeaf = true; } else if (value instanceof MetaContactGroup) { this.setPreferredSize(new Dimension(20, 20)); MetaContactGroup groupItem = (MetaContactGroup) value; this.nameLabel.setText(groupItem.getGroupName()); this.nameLabel.setFont(this.getFont().deriveFont(Font.BOLD)); if (groupForegroundColor != null) this.nameLabel.setForeground(groupForegroundColor); centerPanel.remove(statusMessageLabel); this.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3)); // We should set the bounds of the cell explicitly in order to // make getComponentAt work properly. this.setBounds(0, 0, list.getWidth() - 2, 20); this.statusLabel.setIcon( contactList.isGroupClosed(groupItem) ? closedGroupIcon : openedGroupIcon); // We have no photo icon for groups. this.rightLabel.setIcon(null); this.rightLabel.setText( groupItem.countOnlineChildContacts() + "/" + groupItem.countChildContacts()); this.isLeaf = false; } else if (value instanceof String) { this.setPreferredSize(new Dimension(20, 30)); this.nameLabel.setText((String) value); this.nameLabel.setFont(this.getFont().deriveFont(Font.PLAIN)); } else { this.setPreferredSize(new Dimension(20, 30)); this.nameLabel.setText(value.toString()); this.nameLabel.setFont(this.getFont().deriveFont(Font.PLAIN)); } this.isSelected = isSelected; return this; } /** * Gets the avatar of a specific MetaContact in the form of an * ImageIcon value. * * @param metaContact the MetaContact to retrieve the avatar of * @return an ImageIcon which represents the avatar of the * specified MetaContact */ private ImageIcon getAvatar(MetaContact metaContact) { byte[] avatarBytes = metaContact.getAvatar(true); ImageIcon avatar = null; // Try to get the avatar from the cache. Object[] avatarCache = (Object[]) metaContact.getData(AVATAR_DATA_KEY); if ((avatarCache != null) && (avatarCache[0] == avatarBytes)) avatar = (ImageIcon) avatarCache[1]; // If the avatar isn't available or it's not up-to-date, create it. if ((avatar == null) && (avatarBytes != null) && (avatarBytes.length > 0)) avatar = ImageUtils.getScaledRoundedIcon( avatarBytes, AVATAR_WIDTH, AVATAR_HEIGHT); // Cache the avatar in case it has changed. if (avatarCache == null) { if (avatar != null) metaContact.setData( AVATAR_DATA_KEY, new Object[] { avatarBytes, avatar }); } else { avatarCache[0] = avatarBytes; avatarCache[1] = avatar; } return avatar; } /** * Returns the first found status message for the given * metaContact. * @param metaContact the MetaContact, for which we'd like to * obtain a status message * @return the first found status message for the given * metaContact */ private String getStatusMessage(MetaContact metaContact) { Iterator protoContacts = metaContact.getContacts(); while (protoContacts.hasNext()) { Contact protoContact = protoContacts.next(); String statusMessage = protoContact.getStatusMessage(); if (statusMessage != null && statusMessage.length() > 0) return statusMessage; } return null; } /** * Paints a customized background. * * @param g the Graphics object through which we paint */ @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g = g.create(); try { internalPaintComponent(g); } finally { g.dispose(); } } /** * Paint a background for all groups and a round blue border and background * when a cell is selected. * * @param g the Graphics object through which we paint */ private void internalPaintComponent(Graphics g) { AntialiasingManager.activateAntialiasing(g); Graphics2D g2 = (Graphics2D) g; if (!this.isLeaf) { final int width = getWidth(); GradientPaint p = new GradientPaint(0, 0, Constants.CONTACT_LIST_GROUP_BG_COLOR, width - 5, 0, Constants.CONTACT_LIST_GROUP_BG_GRADIENT_COLOR); g2.setPaint(p); g2.fillRoundRect(1, 1, width - 2, this.getHeight() - 1, 10, 10); } if (this.isSelected) { g2.setColor(Constants.SELECTED_COLOR); g2.fillRoundRect( 1, 1, this.getWidth() - 2, this.getHeight() - 1, 10, 10); } } /** * Returns the height of this icon. * @return the height of this icon */ public int getIconHeight() { return this.getHeight() + 10; } /** * Returns the width of this icon. * @return the widht of this icon */ public int getIconWidth() { return this.getWidth() + 10; } /** * Draw the icon at the specified location. Paints this component as an * icon. * @param c the component which can be used as observer * @param g the Graphics object used for painting * @param x the position on the X coordinate * @param y the position on the Y coordinate */ public void paintIcon(Component c, Graphics g, int x, int y) { Graphics2D g2 = (Graphics2D) g.create(); try { AntialiasingManager.activateAntialiasing(g2); g2.setColor(Color.WHITE); g2.setComposite(AlphaComposite. getInstance(AlphaComposite.SRC_OVER, 0.8f)); g2.fillRoundRect(x, y, getIconWidth() - 1, getIconHeight() - 1, 10, 10); g2.setColor(Color.DARK_GRAY); g2.drawRoundRect(x, y, getIconWidth() - 1, getIconHeight() - 1, 10, 10); // Indent component content from the border. g2.translate(x + 5, y + 5); // Paint component. super.paint(g2); // g2.translate(x, y); } finally { g.dispose(); } } /** * Reloads skin information for this render class. */ public void loadSkin() { openedGroupIcon = new ImageIcon(ImageLoader.getImage(ImageLoader.DOWN_ARROW_ICON)); closedGroupIcon = new ImageIcon(ImageLoader.getImage(ImageLoader.RIGHT_ARROW_ICON)); int groupForegroundProperty = GuiActivator.getResources() .getColor("service.gui.CONTACT_LIST_GROUP_FOREGROUND"); if (groupForegroundProperty > -1) groupForegroundColor = new Color (groupForegroundProperty); int contactForegroundProperty = GuiActivator.getResources() .getColor("service.gui.CONTACT_LIST_CONTACT_FOREGROUND"); if (contactForegroundProperty > -1) contactForegroundColor = new Color(contactForegroundProperty); } }