/**
  * Sets the background of the button depending on its position in the button bar.
  *
  * @param button the button which background to set
  * @param gridX the position of the button in the grid
  * @param isLast indicates if this is the last button in the button bar
  */
 private void setButtonBg(SIPCommButton button, int gridX, boolean isLast) {
   if (!isLast) {
     if (gridX == 1)
       button.setBackgroundImage(ImageLoader.getImage(ImageLoader.CONTACT_LIST_BUTTON_BG_LEFT));
     else if (gridX > 1)
       button.setBackgroundImage(ImageLoader.getImage(ImageLoader.CONTACT_LIST_BUTTON_BG_MIDDLE));
   } else {
     if (gridX == 1) // We have only one button shown.
     button.setBackgroundImage(ImageLoader.getImage(ImageLoader.CONTACT_LIST_ONE_BUTTON_BG));
     else // We set the background of the last button in the toolbar
     button.setBackgroundImage(ImageLoader.getImage(ImageLoader.CONTACT_LIST_BUTTON_BG_RIGHT));
   }
 }
Ejemplo n.º 2
0
 /** Sets the icons of the call buttons depending on the chat session type. */
 private void setCallButtonsIcons() {
   if (chatSession instanceof ConferenceChatSession) {
     callButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_ROOM_CALL));
     callVideoButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_ROOM_VIDEO_CALL));
     callButton.setPreferredSize(new Dimension(29, 25));
     callVideoButton.setPreferredSize(new Dimension(29, 25));
   } else {
     callButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_CALL));
     callVideoButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_VIDEO_CALL));
     callButton.setPreferredSize(new Dimension(25, 25));
     callVideoButton.setPreferredSize(new Dimension(25, 25));
   }
   callButton.repaint();
   callVideoButton.repaint();
 }
Ejemplo n.º 3
0
  /** Reloads icons for buttons. */
  public void loadSkin() {
    inviteButton.setIconImage(ImageLoader.getImage(ImageLoader.ADD_TO_CHAT_ICON));

    historyButton.setIconImage(ImageLoader.getImage(ImageLoader.HISTORY_ICON));

    sendFileButton.setIconImage(ImageLoader.getImage(ImageLoader.SEND_FILE_ICON));

    fontButton.setIconImage(ImageLoader.getImage(ImageLoader.FONT_ICON));

    previousButton.setIconImage(ImageLoader.getImage(ImageLoader.PREVIOUS_ICON));

    nextButton.setIconImage(ImageLoader.getImage(ImageLoader.NEXT_ICON));

    leaveChatRoomButton.setIconImage(ImageLoader.getImage(ImageLoader.LEAVE_ICON));

    callButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_CALL));

    desktopSharingButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_DESKTOP_SHARING));

    optionsButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_CONFIGURE_ICON));
  }
Ejemplo n.º 4
0
  /**
   * Gets the avatar of a specific <tt>MetaContact</tt> in the form of an <tt>ImageIcon</tt> value.
   *
   * @param isSelected indicates if the contact is selected
   * @param width the desired icon width
   * @param height the desired icon height
   * @return an <tt>ImageIcon</tt> which represents the avatar of the specified <tt>MetaContact</tt>
   */
  public ImageIcon getAvatar(boolean isSelected, int width, int height) {
    byte[] avatarBytes = metaContact.getAvatar(true);

    // If there's no avatar we have nothing more to do here.
    if ((avatarBytes == null) || (avatarBytes.length <= 0)) {
      if (!subscribed) {
        return ImageUtils.getScaledRoundedIcon(
            ImageLoader.getImage(ImageLoader.UNAUTHORIZED_CONTACT_PHOTO), width, height);
      }

      return null;
    }

    // If the cell is selected we return a zoomed version of the avatar
    // image.
    if (isSelected) return ImageUtils.getScaledRoundedIcon(avatarBytes, width, height);

    // In any other case try to get the avatar from the cache.
    Object[] avatarCache = (Object[]) metaContact.getData(AVATAR_DATA_KEY);
    ImageIcon avatar = null;

    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) {
      avatar = ImageUtils.getScaledRoundedIcon(avatarBytes, width, 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;
  }
Ejemplo n.º 5
0
/**
 * The <tt>MainToolBar</tt> is a <tt>JToolBar</tt> which contains buttons for file operations, like
 * save and print, for copy-paste operations, etc. It's the main toolbar in the <tt>ChatWindow</tt>.
 * It contains only <tt>ChatToolbarButton</tt>s, which have a specific background icon and rollover
 * behaviour to differentiates them from normal buttons.
 *
 * @author Yana Stamcheva
 * @author Lubomir Marinov
 * @author Adam Netocny
 */
public class MainToolBar extends TransparentPanel
    implements ActionListener, ChatChangeListener, ChatSessionChangeListener, Skinnable {
  /** Serial version UID. */
  private static final long serialVersionUID = -5572510509556499465L;

  /** The invite button. */
  private final ChatToolbarButton inviteButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.ADD_TO_CHAT_ICON));

  /** The history button. */
  private final ChatToolbarButton historyButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.HISTORY_ICON));

  /** The send file button. */
  private final ChatToolbarButton sendFileButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.SEND_FILE_ICON));

  /** The button showing the previous page of the chat history. */
  private final ChatToolbarButton previousButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.PREVIOUS_ICON));

  /** The button showing the next page from chat history. */
  private final ChatToolbarButton nextButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.NEXT_ICON));

  /** The leave chat room button. */
  private final ChatToolbarButton leaveChatRoomButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.LEAVE_ICON));

  /** The call button. */
  private final ChatToolbarButton callButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.CHAT_CALL));

  /** The call button. */
  private final ChatToolbarButton callVideoButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.CHAT_VIDEO_CALL));

  /** The options button. */
  private final ChatToolbarButton optionsButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.CHAT_CONFIGURE_ICON));

  /** The desktop sharing button. */
  private final ChatToolbarButton desktopSharingButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.CHAT_DESKTOP_SHARING));

  /** The font button. */
  private final ChatToolbarButton fontButton =
      new ChatToolbarButton(ImageLoader.getImage(ImageLoader.FONT_ICON));

  private SmileysSelectorBox smileysBox;

  /**
   * The current <tt>ChatSession</tt> made known to this instance by the last call to its {@link
   * #chatChanged(ChatPanel)}.
   */
  private ChatSession chatSession;

  /** The chat container, where this tool bar is added. */
  protected final ChatContainer chatContainer;

  /** The plug-in container contained in this tool bar. */
  private final PluginContainer pluginContainer;

  /** The phone util used to enable/disable buttons. */
  private MetaContactPhoneUtil contactPhoneUtil = null;

  /**
   * Creates an instance and constructs the <tt>MainToolBar</tt>.
   *
   * @param chatContainer The parent <tt>ChatWindow</tt>.
   */
  public MainToolBar(ChatContainer chatContainer) {
    this.chatContainer = chatContainer;

    init();

    pluginContainer = new PluginContainer(this, Container.CONTAINER_CHAT_TOOL_BAR);

    this.chatContainer.addChatChangeListener(this);
  }

  /** Initializes this component. */
  protected void init() {
    this.setLayout(new FlowLayout(FlowLayout.LEFT, 3, 0));
    this.setOpaque(false);

    this.add(inviteButton);

    // if we leave a chat room when we close the window
    // there is no need for this button
    if (!ConfigurationUtils.isLeaveChatRoomOnWindowCloseEnabled()) this.add(leaveChatRoomButton);

    this.add(callButton);
    this.add(callVideoButton);
    this.add(desktopSharingButton);
    this.add(sendFileButton);

    ChatPanel chatPanel = chatContainer.getCurrentChat();
    if (chatPanel == null || !(chatPanel.getChatSession() instanceof MetaContactChatSession))
      sendFileButton.setEnabled(false);

    this.addSeparator();

    this.add(historyButton);
    this.add(previousButton);
    this.add(nextButton);

    // We only add the options button if the property SHOW_OPTIONS_WINDOW
    // specifies so or if it's not set.
    Boolean showOptionsProp =
        GuiActivator.getConfigurationService()
            .getBoolean(ConfigurationFrame.SHOW_OPTIONS_WINDOW_PROPERTY, false);

    if (showOptionsProp.booleanValue()) {
      this.add(optionsButton);
    }

    this.addSeparator();

    if (ConfigurationUtils.isFontSupportEnabled()) {
      this.add(fontButton);
      fontButton.setName("font");
      fontButton.setToolTipText(
          GuiActivator.getResources().getI18NString("service.gui.CHANGE_FONT"));
      fontButton.addActionListener(this);
    }

    initSmiliesSelectorBox();

    this.addSeparator();

    this.inviteButton.setName("invite");
    this.inviteButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.INVITE"));

    this.leaveChatRoomButton.setName("leave");
    this.leaveChatRoomButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.LEAVE"));

    this.callButton.setName("call");
    this.callButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.CALL_CONTACT"));

    this.callVideoButton.setName("callVideo");
    this.callVideoButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.CALL_CONTACT"));

    this.desktopSharingButton.setName("desktop");
    this.desktopSharingButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.SHARE_DESKTOP_WITH_CONTACT"));

    this.historyButton.setName("history");
    this.historyButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.HISTORY") + " Ctrl-H");

    optionsButton.setName("options");
    optionsButton.setToolTipText(GuiActivator.getResources().getI18NString("service.gui.OPTIONS"));

    this.sendFileButton.setName("sendFile");
    this.sendFileButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.SEND_FILE"));

    this.previousButton.setName("previous");
    this.previousButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.PREVIOUS"));

    this.nextButton.setName("next");
    this.nextButton.setToolTipText(GuiActivator.getResources().getI18NString("service.gui.NEXT"));

    inviteButton.addActionListener(this);
    leaveChatRoomButton.addActionListener(this);
    callButton.addActionListener(this);
    callVideoButton.addActionListener(this);
    desktopSharingButton.addActionListener(this);
    historyButton.addActionListener(this);
    optionsButton.addActionListener(this);
    sendFileButton.addActionListener(this);
    previousButton.addActionListener(this);
    nextButton.addActionListener(this);
  }

  private void initSmiliesSelectorBox() {
    this.smileysBox = new SmileysSelectorBox();

    this.smileysBox.setName("smiley");
    this.smileysBox.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.INSERT_SMILEY") + " Ctrl-M");

    SIPCommMenuBar smileyMenuBar = new SIPCommMenuBar();
    smileyMenuBar.setOpaque(false);
    smileyMenuBar.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
    smileyMenuBar.add(smileysBox);

    this.add(smileyMenuBar);
  }

  /**
   * Runs clean-up for associated resources which need explicit disposal (e.g. listeners keeping
   * this instance alive because they were added to the model which operationally outlives this
   * instance).
   */
  public void dispose() {
    pluginContainer.dispose();
  }

  /**
   * Implements ChatChangeListener#chatChanged(ChatPanel).
   *
   * @param chatPanel the <tt>ChatPanel</tt>, which changed
   */
  public void chatChanged(ChatPanel chatPanel) {
    if (chatPanel == null) {
      setChatSession(null);
    } else {
      MetaContact contact = GuiActivator.getUIService().getChatContact(chatPanel);

      for (PluginComponent c : pluginContainer.getPluginComponents()) c.setCurrentContact(contact);

      setChatSession(chatPanel.chatSession);

      leaveChatRoomButton.setEnabled(chatPanel.chatSession instanceof ConferenceChatSession);

      inviteButton.setEnabled(chatPanel.findInviteChatTransport() != null);

      sendFileButton.setEnabled(chatPanel.findFileTransferChatTransport() != null);

      new UpdateCallButtonWorker(contact).start();

      changeHistoryButtonsState(chatPanel);
    }
  }

  /**
   * Returns list of <tt>ChatTransport</tt> (i.e. contact) that supports the specified
   * <tt>OperationSet</tt>.
   *
   * @param transports list of <tt>ChatTransport</tt>
   * @param opSetClass <tt>OperationSet</tt> to find
   * @return list of <tt>ChatTransport</tt> (i.e. contact) that supports the specified
   *     <tt>OperationSet</tt>.
   */
  private List<ChatTransport> getOperationSetForCapabilities(
      List<ChatTransport> transports, Class<? extends OperationSet> opSetClass) {
    List<ChatTransport> list = new ArrayList<ChatTransport>();

    for (ChatTransport transport : transports) {
      ProtocolProviderService protocolProvider = transport.getProtocolProvider();
      OperationSetContactCapabilities capOpSet =
          protocolProvider.getOperationSet(OperationSetContactCapabilities.class);
      OperationSetPersistentPresence presOpSet =
          protocolProvider.getOperationSet(OperationSetPersistentPresence.class);

      if (capOpSet == null) {
        list.add(transport);
      } else if (presOpSet != null) {
        Contact contact = presOpSet.findContactByID(transport.getName());

        if ((contact != null) && (capOpSet.getOperationSet(contact, opSetClass) != null)) {
          // It supports OpSet for at least one of its
          // ChatTransports
          list.add(transport);
        }
      }
    }

    return list;
  }

  /**
   * Implements ChatSessionChangeListener#currentChatTransportChanged(ChatSession).
   *
   * @param chatSession the <tt>ChatSession</tt>, which transport has changed
   */
  public void currentChatTransportChanged(ChatSession chatSession) {
    if (chatSession == null) return;

    ChatTransport currentTransport = chatSession.getCurrentChatTransport();
    Object currentDescriptor = currentTransport.getDescriptor();

    if (currentDescriptor instanceof Contact) {
      Contact contact = (Contact) currentDescriptor;

      for (PluginComponent c : pluginContainer.getPluginComponents()) c.setCurrentContact(contact);
    }
  }

  /**
   * Handles the <tt>ActionEvent</tt>, when one of the tool bar buttons is clicked.
   *
   * @param e the <tt>ActionEvent</tt> that notified us
   */
  public void actionPerformed(ActionEvent e) {
    AbstractButton button = (AbstractButton) e.getSource();
    String buttonText = button.getName();

    ChatPanel chatPanel = chatContainer.getCurrentChat();

    if (buttonText.equals("previous")) {
      chatPanel.loadPreviousPageFromHistory();
    } else if (buttonText.equals("next")) {
      chatPanel.loadNextPageFromHistory();
    } else if (buttonText.equals("sendFile")) {
      SipCommFileChooser scfc =
          GenericFileDialog.create(
              null,
              "Send file...",
              SipCommFileChooser.LOAD_FILE_OPERATION,
              ConfigurationUtils.getSendFileLastDir());
      File selectedFile = scfc.getFileFromDialog();
      if (selectedFile != null) {
        ConfigurationUtils.setSendFileLastDir(selectedFile.getParent());
        chatContainer.getCurrentChat().sendFile(selectedFile);
      }
    } else if (buttonText.equals("history")) {
      HistoryWindow history;

      HistoryWindowManager historyWindowManager =
          GuiActivator.getUIService().getHistoryWindowManager();

      ChatSession chatSession = chatPanel.getChatSession();

      if (historyWindowManager.containsHistoryWindowForContact(chatSession.getDescriptor())) {
        history = historyWindowManager.getHistoryWindowForContact(chatSession.getDescriptor());

        if (history.getState() == JFrame.ICONIFIED) history.setState(JFrame.NORMAL);

        history.toFront();
      } else {
        history = new HistoryWindow(chatPanel.getChatSession().getDescriptor());

        history.setVisible(true);

        historyWindowManager.addHistoryWindowForContact(chatSession.getDescriptor(), history);
      }
    } else if (buttonText.equals("invite")) {
      ChatInviteDialog inviteDialog = new ChatInviteDialog(chatPanel);

      inviteDialog.setVisible(true);
    } else if (buttonText.equals("leave")) {
      ConferenceChatManager conferenceManager =
          GuiActivator.getUIService().getConferenceChatManager();
      conferenceManager.leaveChatRoom((ChatRoomWrapper) chatPanel.getChatSession().getDescriptor());
    } else if (buttonText.equals("call")) {
      call(false, false);
    } else if (buttonText.equals("callVideo")) {
      call(true, false);
    } else if (buttonText.equals("desktop")) {
      call(true, true);
    } else if (buttonText.equals("options")) {
      GuiActivator.getUIService().getConfigurationContainer().setVisible(true);
    } else if (buttonText.equals("font")) chatPanel.showFontChooserDialog();
  }

  /**
   * Returns the button used to show the history window.
   *
   * @return the button used to show the history window.
   */
  public ChatToolbarButton getHistoryButton() {
    return historyButton;
  }

  /**
   * Get the smileys box.
   *
   * @return the smileys box
   */
  public SmileysSelectorBox getSmileysBox() {
    return smileysBox;
  }

  /**
   * Disables/Enables history arrow buttons depending on whether the current page is the first, the
   * last page or a middle page.
   *
   * @param chatPanel the <tt>ChatPanel</tt> which has provoked the change.
   */
  public void changeHistoryButtonsState(ChatPanel chatPanel) {
    ChatConversationPanel convPanel = chatPanel.getChatConversationPanel();

    long firstMsgInHistory = chatPanel.getFirstHistoryMsgTimestamp().getTime();
    long lastMsgInHistory = chatPanel.getLastHistoryMsgTimestamp().getTime();
    Date firstMsgInPage = convPanel.getPageFirstMsgTimestamp();
    Date lastMsgInPage = convPanel.getPageLastMsgTimestamp();

    if (firstMsgInHistory == 0 || lastMsgInHistory == 0) {
      previousButton.setEnabled(false);
      nextButton.setEnabled(false);
      return;
    }

    previousButton.setEnabled(firstMsgInHistory < firstMsgInPage.getTime());

    nextButton.setEnabled(
        (lastMsgInPage.getTime() > 0) && (lastMsgInHistory > lastMsgInPage.getTime()));
  }

  /**
   * Sets the current <tt>ChatSession</tt> made known to this instance by the last call to its
   * {@link #chatChanged(ChatPanel)}.
   *
   * @param chatSession the <tt>ChatSession</tt> to become current for this instance
   */
  private void setChatSession(ChatSession chatSession) {
    if (this.chatSession != chatSession) {
      if (this.chatSession instanceof MetaContactChatSession)
        this.chatSession.removeChatTransportChangeListener(this);

      this.chatSession = chatSession;

      if (this.chatSession instanceof MetaContactChatSession)
        this.chatSession.addChatTransportChangeListener(this);
    }
  }

  /** Adds a separator to this tool bar. */
  private void addSeparator() {
    this.add(new JSeparator(SwingConstants.VERTICAL));
  }

  /** Reloads icons for buttons. */
  public void loadSkin() {
    inviteButton.setIconImage(ImageLoader.getImage(ImageLoader.ADD_TO_CHAT_ICON));

    historyButton.setIconImage(ImageLoader.getImage(ImageLoader.HISTORY_ICON));

    sendFileButton.setIconImage(ImageLoader.getImage(ImageLoader.SEND_FILE_ICON));

    fontButton.setIconImage(ImageLoader.getImage(ImageLoader.FONT_ICON));

    previousButton.setIconImage(ImageLoader.getImage(ImageLoader.PREVIOUS_ICON));

    nextButton.setIconImage(ImageLoader.getImage(ImageLoader.NEXT_ICON));

    leaveChatRoomButton.setIconImage(ImageLoader.getImage(ImageLoader.LEAVE_ICON));

    callButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_CALL));

    desktopSharingButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_DESKTOP_SHARING));

    optionsButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_CONFIGURE_ICON));
  }

  /**
   * Establishes a call.
   *
   * @param isVideo indicates if a video call should be established.
   * @param isDesktopSharing indicates if a desktopSharing should be established.
   */
  private void call(boolean isVideo, boolean isDesktopSharing) {
    ChatPanel chatPanel = chatContainer.getCurrentChat();

    ChatSession chatSession = chatPanel.getChatSession();

    Class<? extends OperationSet> opSetClass;
    if (isVideo) {
      if (isDesktopSharing) opSetClass = OperationSetDesktopSharingServer.class;
      else opSetClass = OperationSetVideoTelephony.class;
    } else opSetClass = OperationSetBasicTelephony.class;

    List<ChatTransport> telTransports = null;
    if (chatSession != null) telTransports = chatSession.getTransportsForOperationSet(opSetClass);

    List<ChatTransport> contactOpSetSupported;

    contactOpSetSupported = getOperationSetForCapabilities(telTransports, opSetClass);

    List<UIContactDetail> res = new ArrayList<UIContactDetail>();
    for (ChatTransport ct : contactOpSetSupported) {
      HashMap<Class<? extends OperationSet>, ProtocolProviderService> m =
          new HashMap<Class<? extends OperationSet>, ProtocolProviderService>();
      m.put(opSetClass, ct.getProtocolProvider());

      UIContactDetailImpl d =
          new UIContactDetailImpl(
              ct.getName(), ct.getDisplayName(), null, null, null, m, null, ct.getName());
      PresenceStatus status = ct.getStatus();
      byte[] statusIconBytes = status.getStatusIcon();

      if (statusIconBytes != null && statusIconBytes.length > 0) {
        d.setStatusIcon(
            new ImageIcon(
                ImageLoader.getIndexedProtocolImage(
                    ImageUtils.getBytesInImage(statusIconBytes), ct.getProtocolProvider())));
      }

      res.add(d);
    }

    Point location = new Point(callButton.getX(), callButton.getY() + callButton.getHeight());

    SwingUtilities.convertPointToScreen(location, this);

    MetaContact metaContact = GuiActivator.getUIService().getChatContact(chatPanel);
    UIContactImpl uiContact = null;
    if (metaContact != null) uiContact = MetaContactListSource.getUIContact(metaContact);

    CallManager.call(res, uiContact, isVideo, isDesktopSharing, callButton, location);
  }

  /**
   * Searches for phone numbers in <tt>MetaContact/tt> operation sets. And changes the call button
   * enable/disable state.
   */
  private class UpdateCallButtonWorker extends SwingWorker {
    /** The current contact. */
    private MetaContact contact;

    /** Has this contact any phone. */
    private boolean isCallEnabled = false;

    /** Has this contact any video phone. */
    private boolean isVideoCallEnabled = false;

    /** Has this contact has desktop sharing enabled. */
    private boolean isDesktopSharingEnabled = false;

    /**
     * Creates worker.
     *
     * @param contact
     */
    UpdateCallButtonWorker(MetaContact contact) {
      this.contact = contact;
    }

    /**
     * Executes in worker thread.
     *
     * @return
     * @throws Exception
     */
    @Override
    protected Object construct() throws Exception {
      contactPhoneUtil = MetaContactPhoneUtil.getPhoneUtil(contact);

      isCallEnabled = contactPhoneUtil.isCallEnabled();
      isVideoCallEnabled = contactPhoneUtil.isVideoCallEnabled();
      isDesktopSharingEnabled = contactPhoneUtil.isDesktopSharingEnabled();

      return null;
    }

    /**
     * Called on the event dispatching thread (not on the worker thread) after the <code>construct
     * </code> method has returned.
     */
    @Override
    protected void finished() {
      callButton.setEnabled(isCallEnabled);
      callVideoButton.setEnabled(isVideoCallEnabled);
      desktopSharingButton.setEnabled(isDesktopSharingEnabled);
    }
  }
}
/**
 * The <tt>ContactListCellRenderer</tt> is the custom cell renderer used in the Jitsi's
 * <tt>ContactList</tt>. 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 ContactListTreeCellRenderer extends JPanel
    implements TreeCellRenderer, Icon, Skinnable {
  /** Serial version UID. */
  private static final long serialVersionUID = 0L;

  /** The default height of the avatar. */
  private static final int AVATAR_HEIGHT = 30;

  /** The default width of the avatar. */
  private static final int AVATAR_WIDTH = 30;

  /** The extended height of the avatar. */
  private static final int EXTENDED_AVATAR_HEIGHT = 45;

  /** The extended width of the avatar. */
  private static final int EXTENDED_AVATAR_WIDTH = 45;

  /** The default width of the button. */
  private static final int BUTTON_WIDTH = 26;

  /** The default height of the button. */
  private static final int BUTTON_HEIGHT = 27;

  /** Left border value. */
  private static final int LEFT_BORDER = 5;

  /** Left border value. */
  private static final int TOP_BORDER = 2;

  /** Bottom border value. */
  private static final int BOTTOM_BORDER = 2;

  /** Right border value. */
  private static final int RIGHT_BORDER = 2;

  /** The horizontal gap between columns in pixels; */
  private static final int H_GAP = 2;

  /** The vertical gap between rows in pixels; */
  private static final int V_GAP = 3;

  /** The separator image for the button toolbar. */
  private static final Image BUTTON_SEPARATOR_IMG =
      ImageLoader.getImage(ImageLoader.CONTACT_LIST_BUTTON_SEPARATOR);

  /** The icon used for opened groups. */
  private ImageIcon openedGroupIcon;

  /** The icon used for closed groups. */
  private ImageIcon closedGroupIcon;

  /** The foreground color for groups. */
  private Color groupForegroundColor;

  /** The foreground color for contacts. */
  private Color contactForegroundColor;

  /** The component showing the name of the contact or group. */
  private final JLabel nameLabel = new JLabel();

  /** The status message label. */
  private final JLabel displayDetailsLabel = new JLabel();

  /** The call button. */
  private final SIPCommButton callButton = new SIPCommButton();

  /** The call video button. */
  private final SIPCommButton callVideoButton = new SIPCommButton();

  /** The desktop sharing button. */
  private final SIPCommButton desktopSharingButton = new SIPCommButton();

  /** The chat button. */
  private final SIPCommButton chatButton = new SIPCommButton();

  /** The add contact button. */
  private final SIPCommButton addContactButton = new SIPCommButton();

  /** The constraints used to align components in the <tt>centerPanel</tt>. */
  private final GridBagConstraints constraints = new GridBagConstraints();

  /** The component showing the avatar or the contact count in the case of groups. */
  protected final JLabel rightLabel = new JLabel();

  /** The message received image. */
  private Image msgReceivedImage;

  /** The label containing the status icon. */
  private final JLabel statusLabel = new JLabel();

  /** The icon showing the contact status. */
  protected Icon statusIcon = new ImageIcon();

  /** Indicates if the current list cell is selected. */
  protected boolean isSelected = false;

  /** The index of the current cell. */
  protected int row = 0;

  /** Indicates if the current cell contains a leaf or a group. */
  protected TreeNode treeNode = null;

  /** The parent tree. */
  private TreeContactList treeContactList;

  /** A list of the custom action buttons for contacts UIContacts. */
  private List<JButton> customActionButtons;

  /** A list of the custom action buttons for groups. */
  private List<JButton> customActionButtonsUIGroup;

  /** The last added button. */
  private SIPCommButton lastAddedButton;

  /** Initializes the panel containing the node. */
  public ContactListTreeCellRenderer() {
    super(new GridBagLayout());

    loadSkin();

    this.setOpaque(true);
    this.nameLabel.setOpaque(false);

    this.displayDetailsLabel.setFont(getFont().deriveFont(9f));
    this.displayDetailsLabel.setForeground(Color.GRAY);

    this.rightLabel.setHorizontalAlignment(JLabel.RIGHT);

    // !! IMPORTANT: General insets used for all components if not
    // overwritten!
    constraints.insets = new Insets(0, 0, 0, H_GAP);

    constraints.anchor = GridBagConstraints.WEST;
    constraints.fill = GridBagConstraints.NONE;
    constraints.gridx = 0;
    constraints.gridy = 0;
    constraints.gridheight = 1;
    constraints.weightx = 0f;
    constraints.weighty = 1f;
    this.add(statusLabel, constraints);

    addLabels(1);

    callButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (treeNode != null && treeNode instanceof ContactNode) {
              call(treeNode, callButton, false, false);
            }
          }
        });

    callVideoButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (treeNode != null && treeNode instanceof ContactNode) {
              call(treeNode, callVideoButton, true, false);
            }
          }
        });

    desktopSharingButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (treeNode != null && treeNode instanceof ContactNode) {
              call(treeNode, desktopSharingButton, true, true);
            }
          }
        });

    chatButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (treeNode != null && treeNode instanceof ContactNode) {
              UIContact contactDescriptor = ((ContactNode) treeNode).getContactDescriptor();

              if (contactDescriptor.getDescriptor() instanceof MetaContact) {
                GuiActivator.getUIService()
                    .getChatWindowManager()
                    .startChat((MetaContact) contactDescriptor.getDescriptor());
              }
            }
          }
        });

    addContactButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (treeNode != null && treeNode instanceof ContactNode) {
              UIContact contactDescriptor = ((ContactNode) treeNode).getContactDescriptor();

              // The add contact function has only sense for external
              // source contacts.
              if (contactDescriptor instanceof SourceUIContact) {
                addContact((SourceUIContact) contactDescriptor);
              }
            }
          }
        });

    initButtonToolTips();
    this.setToolTipText("");
  }

  /**
   * Returns this panel that has been configured to display the meta contact and meta contact group
   * cells.
   *
   * @param tree the source tree
   * @param value the tree node
   * @param selected indicates if the node is selected
   * @param expanded indicates if the node is expanded
   * @param leaf indicates if the node is a leaf
   * @param row indicates the row number of the node
   * @param hasFocus indicates if the node has the focus
   * @return this panel
   */
  public Component getTreeCellRendererComponent(
      JTree tree,
      Object value,
      boolean selected,
      boolean expanded,
      boolean leaf,
      int row,
      boolean hasFocus) {
    this.treeContactList = (TreeContactList) tree;
    this.row = row;
    this.isSelected = selected;
    this.treeNode = (TreeNode) value;

    this.rightLabel.setIcon(null);

    DefaultTreeContactList contactList = (DefaultTreeContactList) tree;

    setBorder();
    addLabels(1);

    // Set background color.
    if (contactList instanceof TreeContactList) {
      ContactListFilter filter = ((TreeContactList) contactList).getCurrentFilter();

      if (filter != null
          && filter.equals(TreeContactList.historyFilter)
          && value instanceof ContactNode
          && row % 2 == 0) {
        setBackground(Constants.CALL_HISTORY_EVEN_ROW_COLOR);
      } else {
        setBackground(Color.WHITE);
      }
    }

    // Make appropriate adjustments for contact nodes and group nodes.
    if (value instanceof ContactNode) {
      UIContactImpl contact = ((ContactNode) value).getContactDescriptor();

      String displayName = contact.getDisplayName();

      if ((displayName == null || displayName.trim().length() < 1)
          && !(contact instanceof ShowMoreContact)) {
        displayName = GuiActivator.getResources().getI18NString("service.gui.UNKNOWN");
      }

      this.nameLabel.setText(displayName);

      if (statusIcon != null
          && contactList.isContactActive(contact)
          && statusIcon instanceof ImageIcon) ((ImageIcon) statusIcon).setImage(msgReceivedImage);
      else statusIcon = contact.getStatusIcon();

      this.statusLabel.setIcon(statusIcon);

      this.nameLabel.setFont(this.getFont().deriveFont(Font.PLAIN));

      if (contactForegroundColor != null) nameLabel.setForeground(contactForegroundColor);

      // Initializes status message components if the given meta contact
      // contains a status message.
      this.initDisplayDetails(contact.getDisplayDetails());

      if (this.treeContactList.isContactButtonsVisible()) this.initButtonsPanel(contact);

      int avatarWidth, avatarHeight;

      if (isSelected && treeContactList.isContactButtonsVisible()) {
        avatarWidth = EXTENDED_AVATAR_WIDTH;
        avatarHeight = EXTENDED_AVATAR_HEIGHT;
      } else {
        avatarWidth = AVATAR_WIDTH;
        avatarHeight = AVATAR_HEIGHT;
      }

      Icon avatar = contact.getAvatar(isSelected, avatarWidth, avatarHeight);

      if (avatar != null) {
        this.rightLabel.setIcon(avatar);
      }

      if (contact instanceof ShowMoreContact) {
        rightLabel.setFont(rightLabel.getFont().deriveFont(12f));
        rightLabel.setForeground(Color.GRAY);
        rightLabel.setText((String) contact.getDescriptor());
      } else {
        rightLabel.setFont(rightLabel.getFont().deriveFont(9f));
        rightLabel.setText("");
      }

      this.setToolTipText(contact.getDescriptor().toString());
    } else if (value instanceof GroupNode) {
      UIGroupImpl groupItem = ((GroupNode) value).getGroupDescriptor();

      this.nameLabel.setText(groupItem.getDisplayName());

      this.nameLabel.setFont(this.getFont().deriveFont(Font.BOLD));

      if (groupForegroundColor != null) this.nameLabel.setForeground(groupForegroundColor);

      this.remove(displayDetailsLabel);
      this.remove(callButton);
      this.remove(callVideoButton);
      this.remove(desktopSharingButton);
      this.remove(chatButton);
      this.remove(addContactButton);

      clearCustomActionButtons();

      statusIcon = expanded ? openedGroupIcon : closedGroupIcon;
      this.statusLabel.setIcon(expanded ? openedGroupIcon : closedGroupIcon);

      // We have no photo icon for groups.
      this.rightLabel.setIcon(null);
      this.rightLabel.setText("");

      if (groupItem.countChildContacts() >= 0) {
        rightLabel.setFont(rightLabel.getFont().deriveFont(9f));
        this.rightLabel.setForeground(Color.BLACK);
        this.rightLabel.setText(
            groupItem.countOnlineChildContacts() + "/" + groupItem.countChildContacts());
      }

      this.initDisplayDetails(groupItem.getDisplayDetails());
      this.initButtonsPanel(groupItem);
      this.setToolTipText(groupItem.getDescriptor().toString());
    }

    return this;
  }

  /**
   * Paints a customized background.
   *
   * @param g the <tt>Graphics</tt> object through which we paint
   */
  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    g = g.create();

    if (!(treeNode instanceof GroupNode) && !isSelected) return;

    AntialiasingManager.activateAntialiasing(g);

    Graphics2D g2 = (Graphics2D) g;

    try {
      internalPaintComponent(g2);
    } finally {
      g.dispose();
    }
  }

  /**
   * Paint a background for all groups and a round blue border and background when a cell is
   * selected.
   *
   * @param g2 the <tt>Graphics2D</tt> object through which we paint
   */
  private void internalPaintComponent(Graphics2D g2) {
    Color borderColor = Color.GRAY;

    if (isSelected) {
      g2.setPaint(
          new GradientPaint(
              0, 0, Constants.SELECTED_COLOR, 0, getHeight(), Constants.SELECTED_GRADIENT_COLOR));

      borderColor = Constants.SELECTED_COLOR;
    } else if (treeNode instanceof GroupNode) {
      g2.setPaint(
          new GradientPaint(
              0,
              0,
              Constants.CONTACT_LIST_GROUP_BG_GRADIENT_COLOR,
              0,
              getHeight(),
              Constants.CONTACT_LIST_GROUP_BG_COLOR));

      borderColor = Constants.CONTACT_LIST_GROUP_BG_COLOR;
    }

    g2.fillRect(0, 0, getWidth(), getHeight());
    g2.setColor(borderColor);
    g2.drawLine(0, 0, getWidth(), 0);
    g2.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1);
  }

  /**
   * Returns the height of this icon. Used for the drag&drop component.
   *
   * @return the height of this icon
   */
  public int getIconHeight() {
    return getPreferredSize().height + 10;
  }

  /**
   * Returns the width of this icon. Used for the drag&drop component.
   *
   * @return the widht of this icon
   */
  public int getIconWidth() {
    return treeContactList.getWidth() + 10;
  }

  /**
   * Returns the preferred size of this component.
   *
   * @return the preferred size of this component
   */
  public Dimension getPreferredSize() {
    Dimension preferredSize = new Dimension();
    int preferredHeight;

    if (treeNode instanceof ContactNode) {
      UIContact contact = ((ContactNode) treeNode).getContactDescriptor();

      preferredHeight = contact.getPreferredHeight();

      if (preferredHeight > 0) preferredSize.height = preferredHeight;
      else if (contact instanceof ShowMoreContact) preferredSize.height = 20;
      else if (isSelected && treeContactList.isContactButtonsVisible()) preferredSize.height = 70;
      else preferredSize.height = 35;
    } else if (treeNode instanceof GroupNode) {
      UIGroup group = ((GroupNode) treeNode).getGroupDescriptor();

      preferredHeight = group.getPreferredHeight();

      if (isSelected && customActionButtonsUIGroup != null && !customActionButtonsUIGroup.isEmpty())
        preferredSize.height = 70;
      else if (preferredHeight > 0) preferredSize.height = preferredHeight;
      else preferredSize.height = 20;
    }

    return preferredSize;
  }

  /**
   * Adds contact entry labels.
   *
   * @param nameLabelGridWidth the grid width of the contact entry name label
   */
  private void addLabels(int nameLabelGridWidth) {
    remove(nameLabel);
    remove(rightLabel);
    remove(displayDetailsLabel);

    if (treeNode != null && !(treeNode instanceof GroupNode))
      constraints.insets = new Insets(0, 0, V_GAP, H_GAP);
    else constraints.insets = new Insets(0, 0, 0, H_GAP);

    constraints.anchor = GridBagConstraints.WEST;
    constraints.fill = GridBagConstraints.NONE;
    constraints.gridx = 1;
    constraints.gridy = 0;
    constraints.weightx = 1f;
    constraints.weighty = 0f;
    constraints.gridheight = 1;
    constraints.gridwidth = nameLabelGridWidth;
    this.add(nameLabel, constraints);

    constraints.anchor = GridBagConstraints.NORTHEAST;
    constraints.fill = GridBagConstraints.VERTICAL;
    constraints.gridx = nameLabelGridWidth + 1;
    constraints.gridy = 0;
    constraints.gridheight = 3;
    constraints.weightx = 0f;
    constraints.weighty = 1f;
    this.add(rightLabel, constraints);

    if (treeNode != null && treeNode instanceof ContactNode) {
      constraints.anchor = GridBagConstraints.WEST;
      constraints.fill = GridBagConstraints.NONE;
      constraints.gridx = 1;
      constraints.gridy = 1;
      constraints.weightx = 1f;
      constraints.weighty = 0f;
      constraints.gridwidth = nameLabelGridWidth;
      constraints.gridheight = 1;

      this.add(displayDetailsLabel, constraints);
    } else if (treeNode != null && treeNode instanceof GroupNode) {
      constraints.anchor = GridBagConstraints.WEST;
      constraints.fill = GridBagConstraints.NONE;
      constraints.gridx = 1;
      constraints.gridy = 1;
      constraints.weightx = 1f;
      constraints.weighty = 0f;
      constraints.gridwidth = nameLabelGridWidth;
      constraints.gridheight = 1;

      this.add(displayDetailsLabel, constraints);
    }
  }

  /**
   * Initializes the display details component for the given <tt>UIContact</tt>.
   *
   * @param displayDetails the display details to show
   */
  private void initDisplayDetails(String displayDetails) {
    remove(displayDetailsLabel);
    displayDetailsLabel.setText("");

    if (displayDetails != null && displayDetails.length() > 0) {
      // Replace all occurrences of new line with slash.
      displayDetails = Html2Text.extractText(displayDetails);
      displayDetails = displayDetails.replaceAll("\n|<br>|<br/>", " / ");

      displayDetailsLabel.setText(displayDetails);
    }

    constraints.anchor = GridBagConstraints.WEST;
    constraints.fill = GridBagConstraints.NONE;
    constraints.gridx = 1;
    constraints.gridy = 1;
    constraints.weightx = 1f;
    constraints.weighty = 0f;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;

    this.add(displayDetailsLabel, constraints);
  }

  /**
   * Initializes buttons panel.
   *
   * @param uiContact the <tt>UIContact</tt> for which we initialize the button panel
   */
  private void initButtonsPanel(UIContact uiContact) {
    this.remove(chatButton);
    this.remove(callButton);
    this.remove(callVideoButton);
    this.remove(desktopSharingButton);
    this.remove(addContactButton);

    clearCustomActionButtons();

    if (!isSelected) return;

    UIContactDetail imContact = null;
    // For now we support instance messaging only for contacts in our
    // contact list until it's implemented for external source contacts.
    if (uiContact.getDescriptor() instanceof MetaContact)
      imContact = uiContact.getDefaultContactDetail(OperationSetBasicInstantMessaging.class);

    int x = (statusIcon == null ? 0 : statusIcon.getIconWidth()) + LEFT_BORDER + H_GAP;

    // Re-initialize the x grid.
    constraints.gridx = 0;
    int gridX = 0;

    if (imContact != null) {
      x += addButton(chatButton, ++gridX, x, false);
    }

    UIContactDetail telephonyContact =
        uiContact.getDefaultContactDetail(OperationSetBasicTelephony.class);

    // Check if contact has additional phone numbers, if yes show the
    // call button
    ContactPhoneUtil contactPhoneUtil = null;

    // check for phone stored in contact info only
    // if telephony contact is missing
    if (uiContact.getDescriptor() != null
        && uiContact.getDescriptor() instanceof MetaContact
        && telephonyContact == null) {
      contactPhoneUtil = ContactPhoneUtil.getPhoneUtil((MetaContact) uiContact.getDescriptor());

      MetaContact metaContact = (MetaContact) uiContact.getDescriptor();
      Iterator<Contact> contacts = metaContact.getContacts();

      while (contacts.hasNext()) // && !hasPhone)
      {
        Contact contact = contacts.next();

        if (!contact.getProtocolProvider().isRegistered()) continue;

        contactPhoneUtil.addDetailsResponseListener(
            contact, new DetailsListener(treeNode, callButton, uiContact));
      }
    }

    // for SourceContact in history that do not support telephony, we
    // show the button but disabled
    List<ProtocolProviderService> providers =
        AccountUtils.getOpSetRegisteredProviders(OperationSetBasicTelephony.class, null, null);

    if ((telephonyContact != null && telephonyContact.getAddress() != null)
        || (contactPhoneUtil != null && contactPhoneUtil.isCallEnabled() && providers.size() > 0)) {
      x += addButton(callButton, ++gridX, x, false);
    }

    UIContactDetail videoContact =
        uiContact.getDefaultContactDetail(OperationSetVideoTelephony.class);

    if (videoContact != null
        || (contactPhoneUtil != null && contactPhoneUtil.isVideoCallEnabled())) {
      x += addButton(callVideoButton, ++gridX, x, false);
    }

    UIContactDetail desktopContact =
        uiContact.getDefaultContactDetail(OperationSetDesktopSharingServer.class);

    if (desktopContact != null
        || (contactPhoneUtil != null && contactPhoneUtil.isDesktopSharingEnabled())) {
      x += addButton(desktopSharingButton, ++gridX, x, false);
    }

    // enable add contact button if contact source has indicated
    // that this is possible
    if (uiContact.getDescriptor() instanceof SourceContact
        && uiContact.getDefaultContactDetail(OperationSetPersistentPresence.class) != null
        && AccountUtils.getOpSetRegisteredProviders(
                    OperationSetPersistentPresence.class, null, null)
                .size()
            > 0
        && !ConfigurationUtils.isAddContactDisabled()) {
      x += addButton(addContactButton, ++gridX, x, false);
    }

    // The list of the contact actions
    // we will create a button for every action
    Collection<SIPCommButton> contactActions = uiContact.getContactCustomActionButtons();

    int lastGridX = gridX;
    if (contactActions != null && contactActions.size() > 0) {
      lastGridX = initContactActionButtons(contactActions, gridX, x);
    } else {
      addLabels(gridX);
    }

    if (lastAddedButton != null) setButtonBg(lastAddedButton, lastGridX, true);

    this.setBounds(0, 0, treeContactList.getWidth(), getPreferredSize().height);
  }

  /**
   * Initializes buttons panel.
   *
   * @param uiGroup the <tt>UIGroup</tt> for which we initialize the button panel
   */
  private void initButtonsPanel(UIGroup uiGroup) {
    if (!isSelected) return;

    int x = (statusIcon == null ? 0 : statusIcon.getIconWidth()) + LEFT_BORDER + H_GAP;
    int gridX = 0;

    // The list of the actions
    // we will create a button for every action
    Collection<SIPCommButton> contactActions = uiGroup.getCustomActionButtons();

    int lastGridX = gridX;
    if (contactActions != null && contactActions.size() > 0) {
      lastGridX = initGroupActionButtons(contactActions, gridX, x);
    } else {
      addLabels(gridX);
    }

    if (lastAddedButton != null) setButtonBg(lastAddedButton, lastGridX, true);

    this.setBounds(0, 0, treeContactList.getWidth(), getPreferredSize().height);
  }

  /** Clears the custom action buttons. */
  private void clearCustomActionButtons() {
    if (customActionButtons != null && customActionButtons.size() > 0) {
      Iterator<JButton> buttonsIter = customActionButtons.iterator();
      while (buttonsIter.hasNext()) {
        remove(buttonsIter.next());
      }
      customActionButtons.clear();
    }

    if (customActionButtonsUIGroup != null && customActionButtonsUIGroup.size() > 0) {
      Iterator<JButton> buttonsIter = customActionButtonsUIGroup.iterator();
      while (buttonsIter.hasNext()) {
        remove(buttonsIter.next());
      }
      customActionButtonsUIGroup.clear();
    }
  }

  /**
   * Initializes custom contact action buttons.
   *
   * @param contactActionButtons the list of buttons to initialize
   * @param gridX the X grid of the first button
   * @param xBounds the x bounds of the first button
   * @return the new grid X coordinate after adding all the buttons
   */
  private int initGroupActionButtons(
      Collection<SIPCommButton> contactActionButtons, int gridX, int xBounds) {
    // Reinit the labels to take the whole horizontal space.
    addLabels(gridX + contactActionButtons.size());

    Iterator<SIPCommButton> actionsIter = contactActionButtons.iterator();
    while (actionsIter.hasNext()) {
      final SIPCommButton actionButton = actionsIter.next();

      // We need to explicitly remove the buttons from the tooltip manager,
      // because we're going to manager the tooltip ourselves in the
      // DefaultTreeContactList class. We need to do this in order to have
      // a different tooltip for every button and for non button area.
      ToolTipManager.sharedInstance().unregisterComponent(actionButton);

      if (customActionButtonsUIGroup == null)
        customActionButtonsUIGroup = new LinkedList<JButton>();

      customActionButtonsUIGroup.add(actionButton);

      xBounds += addButton(actionButton, ++gridX, xBounds, false);
    }

    return gridX;
  }

  /**
   * Initializes custom contact action buttons.
   *
   * @param contactActionButtons the list of buttons to initialize
   * @param gridX the X grid of the first button
   * @param xBounds the x bounds of the first button
   * @return the new grid X coordiante after adding all the buttons
   */
  private int initContactActionButtons(
      Collection<SIPCommButton> contactActionButtons, int gridX, int xBounds) {
    // Reinit the labels to take the whole horizontal space.
    addLabels(gridX + contactActionButtons.size());

    Iterator<SIPCommButton> actionsIter = contactActionButtons.iterator();
    while (actionsIter.hasNext()) {
      final SIPCommButton actionButton = actionsIter.next();

      // We need to explicitly remove the buttons from the tooltip manager,
      // because we're going to manager the tooltip ourselves in the
      // DefaultTreeContactList class. We need to do this in order to have
      // a different tooltip for every button and for non button area.
      ToolTipManager.sharedInstance().unregisterComponent(actionButton);

      if (customActionButtons == null) customActionButtons = new LinkedList<JButton>();

      customActionButtons.add(actionButton);

      xBounds += addButton(actionButton, ++gridX, xBounds, false);
    }

    return gridX;
  }

  /**
   * 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 <tt>Graphics</tt> 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) {
    g = g.create();
    try {
      Graphics2D g2 = (Graphics2D) g;
      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);

      super.paint(g2);

      g2.translate(x, y);
    } finally {
      g.dispose();
    }
  }

  /**
   * Returns the call button contained in the current cell.
   *
   * @return the call button contained in the current cell
   */
  public JButton getChatButton() {
    return chatButton;
  }

  /**
   * Returns the call button contained in the current cell.
   *
   * @return the call button contained in the current cell
   */
  public JButton getCallButton() {
    return callButton;
  }

  /**
   * Returns the call video button contained in the current cell.
   *
   * @return the call video button contained in the current cell
   */
  public JButton getCallVideoButton() {
    return callVideoButton;
  }

  /**
   * Returns the desktop sharing button contained in the current cell.
   *
   * @return the desktop sharing button contained in the current cell
   */
  public JButton getDesktopSharingButton() {
    return desktopSharingButton;
  }

  /**
   * Returns the add contact button contained in the current cell.
   *
   * @return the add contact button contained in the current cell
   */
  public JButton getAddContactButton() {
    return addContactButton;
  }

  /**
   * Calls the given treeNode.
   *
   * @param treeNode the <tt>TreeNode</tt> to call
   */
  private void call(TreeNode treeNode, JButton button, boolean isVideo, boolean isDesktopSharing) {
    if (!(treeNode instanceof ContactNode)) return;

    UIContact contactDescriptor = ((ContactNode) treeNode).getContactDescriptor();

    Point location = new Point(button.getX(), button.getY() + button.getHeight());

    SwingUtilities.convertPointToScreen(location, treeContactList);

    location.y = location.y + treeContactList.getPathBounds(treeContactList.getSelectionPath()).y;
    location.x += 8;
    location.y -= 8;

    CallManager.call(contactDescriptor, isVideo, isDesktopSharing, treeContactList, location);
  }

  /**
   * Shows the appropriate user interface that would allow the user to add the given
   * <tt>SourceUIContact</tt> to their contact list.
   *
   * @param contact the contact to add
   */
  private void addContact(SourceUIContact contact) {
    SourceContact sourceContact = (SourceContact) contact.getDescriptor();

    List<ContactDetail> details =
        sourceContact.getContactDetails(OperationSetPersistentPresence.class);
    int detailsCount = details.size();

    if (detailsCount > 1) {
      JMenuItem addContactMenu =
          TreeContactList.createAddContactMenu((SourceContact) contact.getDescriptor());

      JPopupMenu popupMenu = ((JMenu) addContactMenu).getPopupMenu();

      // Add a title label.
      JLabel infoLabel = new JLabel();
      infoLabel.setText(
          "<html><b>"
              + GuiActivator.getResources().getI18NString("service.gui.ADD_CONTACT")
              + "</b></html>");

      popupMenu.insert(infoLabel, 0);
      popupMenu.insert(new Separator(), 1);

      popupMenu.setFocusable(true);
      popupMenu.setInvoker(treeContactList);

      Point location =
          new Point(
              addContactButton.getX(), addContactButton.getY() + addContactButton.getHeight());

      SwingUtilities.convertPointToScreen(location, treeContactList);

      location.y = location.y + treeContactList.getPathBounds(treeContactList.getSelectionPath()).y;

      popupMenu.setLocation(location.x + 8, location.y - 8);
      popupMenu.setVisible(true);
    } else if (details.size() == 1) {
      TreeContactList.showAddContactDialog(details.get(0), sourceContact.getDisplayName());
    }
  }

  /**
   * Returns the drag icon used to represent a cell in all drag operations.
   *
   * @param tree the parent tree object
   * @param dragObject the dragged object
   * @param index the index of the dragged object in the tree
   * @return the drag icon
   */
  public Icon getDragIcon(JTree tree, Object dragObject, int index) {
    ContactListTreeCellRenderer dragC =
        (ContactListTreeCellRenderer)
            getTreeCellRendererComponent(
                tree,
                dragObject,
                false, // is selected
                false, // is expanded
                true, // is leaf
                index,
                true // has focus
                );

    // We should explicitly set the bounds of all components in order that
    // they're correctly painted by paintIcon afterwards. This fixes empty
    // drag component in contact list!
    dragC.setBounds(0, 0, dragC.getIconWidth(), dragC.getIconHeight());

    Icon rightLabelIcon = rightLabel.getIcon();
    int imageHeight = 0;
    int imageWidth = 0;
    if (rightLabelIcon != null) {
      imageWidth = rightLabelIcon.getIconWidth();
      imageHeight = rightLabelIcon.getIconHeight();
      dragC.rightLabel.setBounds(tree.getWidth() - imageWidth, 0, imageWidth, imageHeight);
    }

    dragC.statusLabel.setBounds(0, 0, statusLabel.getWidth(), statusLabel.getHeight());

    dragC.nameLabel.setBounds(
        statusLabel.getWidth(), 0, tree.getWidth() - imageWidth - 5, nameLabel.getHeight());

    dragC.displayDetailsLabel.setBounds(
        displayDetailsLabel.getX(),
        nameLabel.getHeight(),
        displayDetailsLabel.getWidth(),
        displayDetailsLabel.getHeight());

    return dragC;
  }

  /** Resets the rollover state of all rollover components in the current cell. */
  public void resetRolloverState() {
    chatButton.getModel().setRollover(false);
    callButton.getModel().setRollover(false);
    callVideoButton.getModel().setRollover(false);
    desktopSharingButton.getModel().setRollover(false);
    addContactButton.getModel().setRollover(false);

    if (customActionButtons != null) {
      Iterator<JButton> buttonsIter = customActionButtons.iterator();
      while (buttonsIter.hasNext()) {
        JButton button = buttonsIter.next();
        button.getModel().setRollover(false);
      }
    }

    if (customActionButtonsUIGroup != null) {
      Iterator<JButton> buttonsIter = customActionButtonsUIGroup.iterator();
      while (buttonsIter.hasNext()) {
        JButton button = buttonsIter.next();
        button.getModel().setRollover(false);
      }
    }
  }

  /**
   * Resets the rollover state of all rollover components in the current cell except the component
   * given as a parameter.
   *
   * @param excludeComponent the component to exclude from the reset
   */
  public void resetRolloverState(Component excludeComponent) {
    if (!chatButton.equals(excludeComponent)) chatButton.getModel().setRollover(false);

    if (!callButton.equals(excludeComponent)) callButton.getModel().setRollover(false);

    if (!callVideoButton.equals(excludeComponent)) callVideoButton.getModel().setRollover(false);

    if (!desktopSharingButton.equals(excludeComponent))
      desktopSharingButton.getModel().setRollover(false);

    if (!addContactButton.equals(excludeComponent)) addContactButton.getModel().setRollover(false);

    if (customActionButtons != null) {
      Iterator<JButton> buttonsIter = customActionButtons.iterator();
      while (buttonsIter.hasNext()) {
        JButton button = buttonsIter.next();

        if (!button.equals(excludeComponent)) button.getModel().setRollover(false);
      }
    }

    if (customActionButtonsUIGroup != null) {
      Iterator<JButton> buttonsIter = customActionButtonsUIGroup.iterator();
      while (buttonsIter.hasNext()) {
        JButton button = buttonsIter.next();

        if (!button.equals(excludeComponent)) button.getModel().setRollover(false);
      }
    }
  }

  /** Loads all images and colors. */
  public void loadSkin() {
    openedGroupIcon = new ImageIcon(ImageLoader.getImage(ImageLoader.OPENED_GROUP_ICON));

    closedGroupIcon = new ImageIcon(ImageLoader.getImage(ImageLoader.CLOSED_GROUP_ICON));

    callButton.setIconImage(ImageLoader.getImage(ImageLoader.CALL_BUTTON_SMALL));
    callButton.setRolloverIcon(ImageLoader.getImage(ImageLoader.CALL_BUTTON_SMALL_ROLLOVER));
    callButton.setPressedIcon(ImageLoader.getImage(ImageLoader.CALL_BUTTON_SMALL_PRESSED));

    chatButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_BUTTON_SMALL));
    chatButton.setRolloverIcon(ImageLoader.getImage(ImageLoader.CHAT_BUTTON_SMALL_ROLLOVER));
    chatButton.setPressedIcon(ImageLoader.getImage(ImageLoader.CHAT_BUTTON_SMALL_PRESSED));

    msgReceivedImage = ImageLoader.getImage(ImageLoader.MESSAGE_RECEIVED_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);

    callVideoButton.setIconImage(ImageLoader.getImage(ImageLoader.CALL_VIDEO_BUTTON_SMALL));
    callVideoButton.setRolloverIcon(
        ImageLoader.getImage(ImageLoader.CALL_VIDEO_BUTTON_SMALL_ROLLOVER));
    callVideoButton.setPressedIcon(
        ImageLoader.getImage(ImageLoader.CALL_VIDEO_BUTTON_SMALL_PRESSED));

    desktopSharingButton.setIconImage(ImageLoader.getImage(ImageLoader.DESKTOP_BUTTON_SMALL));
    desktopSharingButton.setRolloverIcon(
        ImageLoader.getImage(ImageLoader.DESKTOP_BUTTON_SMALL_ROLLOVER));
    desktopSharingButton.setPressedIcon(
        ImageLoader.getImage(ImageLoader.DESKTOP_BUTTON_SMALL_PRESSED));

    addContactButton.setIconImage(ImageLoader.getImage(ImageLoader.ADD_CONTACT_BUTTON_SMALL));
    addContactButton.setRolloverIcon(
        ImageLoader.getImage(ImageLoader.ADD_CONTACT_BUTTON_SMALL_ROLLOVER));
    addContactButton.setPressedIcon(
        ImageLoader.getImage(ImageLoader.ADD_CONTACT_BUTTON_SMALL_PRESSED));
  }

  /**
   * Listens for contact details if not cached, we will receive when they are retrieved to update
   * current call button state, if meanwhile user hasn't changed the current contact.
   */
  private class DetailsListener
      implements OperationSetServerStoredContactInfo.DetailsResponseListener {
    /** The source this listener is created for, if current tree node changes ignore any event. */
    private Object source;

    /** The button to change. */
    private JButton callButton;

    /** The ui contact to update after changes. */
    private UIContact uiContact;

    /**
     * Create listener.
     *
     * @param source the contact this listener is for, if different than current ignore.
     * @param callButton
     * @param uiContact the contact to refresh
     */
    DetailsListener(Object source, JButton callButton, UIContact uiContact) {
      this.source = source;
      this.callButton = callButton;
      this.uiContact = uiContact;
    }

    /**
     * Details have been retrieved.
     *
     * @param details the details retrieved if any.
     */
    public void detailsRetrieved(Iterator<GenericDetail> details) {
      // if treenode has changed ignore
      if (!source.equals(treeNode)) return;

      while (details.hasNext()) {
        GenericDetail d = details.next();

        if (d instanceof PhoneNumberDetail
            && !(d instanceof PagerDetail)
            && !(d instanceof FaxDetail)) {
          final PhoneNumberDetail pnd = (PhoneNumberDetail) d;
          if (pnd.getNumber() != null && pnd.getNumber().length() > 0) {
            SwingUtilities.invokeLater(
                new Runnable() {
                  public void run() {
                    callButton.setEnabled(true);

                    if (pnd instanceof VideoDetail) {
                      callVideoButton.setEnabled(true);
                      desktopSharingButton.setEnabled(true);
                    }

                    treeContactList.refreshContact(uiContact);
                  }
                });

            return;
          }
        }
      }
    }
  }

  private int addButton(SIPCommButton button, int gridX, int xBounds, boolean isLast) {
    lastAddedButton = button;

    constraints.insets = new Insets(0, 0, V_GAP, 0);
    constraints.anchor = GridBagConstraints.WEST;
    constraints.fill = GridBagConstraints.NONE;
    constraints.gridx = gridX;
    constraints.gridy = 2;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0f;
    constraints.weighty = 0f;
    this.add(button, constraints);

    int yBounds =
        TOP_BORDER
            + BOTTOM_BORDER
            + 2 * V_GAP
            + ComponentUtils.getStringSize(nameLabel, nameLabel.getText()).height
            + ComponentUtils.getStringSize(displayDetailsLabel, displayDetailsLabel.getText())
                .height;

    button.setBounds(xBounds, yBounds, BUTTON_WIDTH, BUTTON_HEIGHT);

    button.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));

    setButtonBg(button, gridX, isLast);

    return button.getWidth();
  }

  /**
   * Sets the background of the button depending on its position in the button bar.
   *
   * @param button the button which background to set
   * @param gridX the position of the button in the grid
   * @param isLast indicates if this is the last button in the button bar
   */
  private void setButtonBg(SIPCommButton button, int gridX, boolean isLast) {
    if (!isLast) {
      if (gridX == 1)
        button.setBackgroundImage(ImageLoader.getImage(ImageLoader.CONTACT_LIST_BUTTON_BG_LEFT));
      else if (gridX > 1)
        button.setBackgroundImage(ImageLoader.getImage(ImageLoader.CONTACT_LIST_BUTTON_BG_MIDDLE));
    } else {
      if (gridX == 1) // We have only one button shown.
      button.setBackgroundImage(ImageLoader.getImage(ImageLoader.CONTACT_LIST_ONE_BUTTON_BG));
      else // We set the background of the last button in the toolbar
      button.setBackgroundImage(ImageLoader.getImage(ImageLoader.CONTACT_LIST_BUTTON_BG_RIGHT));
    }
  }

  /** Sets the correct border depending on the contained object. */
  private void setBorder() {
    /*
     * !!! When changing border values we should make sure that we
     * recalculate the X and Y coordinates of the buttons added in
     * initButtonsPanel and initContactActionButtons functions. If not
     * correctly calculated problems may occur when clicking buttons!
     */
    if (treeNode instanceof ContactNode
        && !(((ContactNode) treeNode).getContactDescriptor() instanceof ShowMoreContact)) {
      this.setBorder(
          BorderFactory.createEmptyBorder(TOP_BORDER, LEFT_BORDER, BOTTOM_BORDER, RIGHT_BORDER));
    } else // GroupNode || ShowMoreContact
    {
      this.setBorder(BorderFactory.createEmptyBorder(0, LEFT_BORDER, 0, RIGHT_BORDER));
    }
  }

  /** Inializes button tool tips. */
  private void initButtonToolTips() {
    callButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.CALL_CONTACT"));
    callVideoButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.VIDEO_CALL"));
    desktopSharingButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.SHARE_DESKTOP"));
    chatButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.SEND_MESSAGE"));
    addContactButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.ADD_CONTACT"));

    // We need to explicitly remove the buttons from the tooltip manager,
    // because we're going to manager the tooltip ourselves in the
    // DefaultTreeContactList class. We need to do this in order to have
    // a different tooltip for every button and for non button area.
    ToolTipManager ttManager = ToolTipManager.sharedInstance();
    ttManager.unregisterComponent(callButton);
    ttManager.unregisterComponent(callVideoButton);
    ttManager.unregisterComponent(desktopSharingButton);
    ttManager.unregisterComponent(chatButton);
    ttManager.unregisterComponent(addContactButton);
  }
}
  /** Loads all images and colors. */
  public void loadSkin() {
    openedGroupIcon = new ImageIcon(ImageLoader.getImage(ImageLoader.OPENED_GROUP_ICON));

    closedGroupIcon = new ImageIcon(ImageLoader.getImage(ImageLoader.CLOSED_GROUP_ICON));

    callButton.setIconImage(ImageLoader.getImage(ImageLoader.CALL_BUTTON_SMALL));
    callButton.setRolloverIcon(ImageLoader.getImage(ImageLoader.CALL_BUTTON_SMALL_ROLLOVER));
    callButton.setPressedIcon(ImageLoader.getImage(ImageLoader.CALL_BUTTON_SMALL_PRESSED));

    chatButton.setIconImage(ImageLoader.getImage(ImageLoader.CHAT_BUTTON_SMALL));
    chatButton.setRolloverIcon(ImageLoader.getImage(ImageLoader.CHAT_BUTTON_SMALL_ROLLOVER));
    chatButton.setPressedIcon(ImageLoader.getImage(ImageLoader.CHAT_BUTTON_SMALL_PRESSED));

    msgReceivedImage = ImageLoader.getImage(ImageLoader.MESSAGE_RECEIVED_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);

    callVideoButton.setIconImage(ImageLoader.getImage(ImageLoader.CALL_VIDEO_BUTTON_SMALL));
    callVideoButton.setRolloverIcon(
        ImageLoader.getImage(ImageLoader.CALL_VIDEO_BUTTON_SMALL_ROLLOVER));
    callVideoButton.setPressedIcon(
        ImageLoader.getImage(ImageLoader.CALL_VIDEO_BUTTON_SMALL_PRESSED));

    desktopSharingButton.setIconImage(ImageLoader.getImage(ImageLoader.DESKTOP_BUTTON_SMALL));
    desktopSharingButton.setRolloverIcon(
        ImageLoader.getImage(ImageLoader.DESKTOP_BUTTON_SMALL_ROLLOVER));
    desktopSharingButton.setPressedIcon(
        ImageLoader.getImage(ImageLoader.DESKTOP_BUTTON_SMALL_PRESSED));

    addContactButton.setIconImage(ImageLoader.getImage(ImageLoader.ADD_CONTACT_BUTTON_SMALL));
    addContactButton.setRolloverIcon(
        ImageLoader.getImage(ImageLoader.ADD_CONTACT_BUTTON_SMALL_ROLLOVER));
    addContactButton.setPressedIcon(
        ImageLoader.getImage(ImageLoader.ADD_CONTACT_BUTTON_SMALL_PRESSED));
  }