/**
   * Sends a typing notification state.
   *
   * @param typingState the typing notification state to send
   * @return the result of this operation. One of the TYPING_NOTIFICATION_XXX constants defined in
   *     this class
   */
  public int sendTypingNotification(int typingState) {
    // If this chat transport does not support sms messaging we do
    // nothing here.
    if (!allowsTypingNotifications()) return -1;

    ProtocolProviderService protocolProvider = contact.getProtocolProvider();
    OperationSetTypingNotifications tnOperationSet =
        protocolProvider.getOperationSet(OperationSetTypingNotifications.class);

    // if protocol is not registered or contact is offline don't
    // try to send typing notifications
    if (protocolProvider.isRegistered()
        && contact.getPresenceStatus().getStatus() >= PresenceStatus.ONLINE_THRESHOLD) {
      try {
        tnOperationSet.sendTypingNotification(contact, typingState);

        return ChatPanel.TYPING_NOTIFICATION_SUCCESSFULLY_SENT;
      } catch (Exception ex) {
        logger.error("Failed to send typing notifications.", ex);

        return ChatPanel.TYPING_NOTIFICATION_SEND_FAILED;
      }
    }

    return ChatPanel.TYPING_NOTIFICATION_SEND_FAILED;
  }
  /**
   * Sets the icon for the given file.
   *
   * @param file the file to set an icon for
   * @return the byte array containing the thumbnail
   */
  private byte[] getFileThumbnail(File file) {
    byte[] bytes = null;
    if (FileUtils.isImage(file.getName())) {
      try {
        ImageIcon image = new ImageIcon(file.toURI().toURL());
        int width = image.getIconWidth();
        int height = image.getIconHeight();

        if (width > THUMBNAIL_WIDTH) width = THUMBNAIL_WIDTH;
        if (height > THUMBNAIL_HEIGHT) height = THUMBNAIL_HEIGHT;

        bytes = ImageUtils.getScaledInstanceInBytes(image.getImage(), width, height);
      } catch (MalformedURLException e) {
        if (logger.isDebugEnabled()) logger.debug("Could not locate image.", e);
      }
    }
    return bytes;
  }
/**
 * The <tt>NewStatusMessageDialog</tt> is the dialog containing the form for changing the status
 * message for a protocol provider.
 *
 * @author Yana Stamcheva
 * @author Adam Netocny
 */
public class NewStatusMessageDialog extends SIPCommDialog implements ActionListener, Skinnable {
  /** The Object used for logging. */
  private final Logger logger = Logger.getLogger(NewStatusMessageDialog.class);

  /** The field, containing the status message. */
  private final JTextField messageTextField = new JTextField();

  /** The button, used to cancel this dialog. */
  private final JButton cancelButton =
      new JButton(GuiActivator.getResources().getI18NString("service.gui.CANCEL"));

  /** The presence operation set through which we change the status message. */
  private final OperationSetPresence presenceOpSet;

  /** Message panel. */
  private JPanel messagePanel;

  /**
   * Creates an instance of <tt>NewStatusMessageDialog</tt>.
   *
   * @param protocolProvider the <tt>ProtocolProviderService</tt>.
   */
  public NewStatusMessageDialog(ProtocolProviderService protocolProvider) {
    presenceOpSet =
        (OperationSetPersistentPresence)
            protocolProvider.getOperationSet(OperationSetPresence.class);

    this.init();
    pack();
  }

  /** Initializes the <tt>NewStatusMessageDialog</tt> by adding the buttons, fields, etc. */
  private void init() {
    JLabel messageLabel =
        new JLabel(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE"));

    JPanel dataPanel = new TransparentPanel(new BorderLayout(5, 5));

    JTextArea infoArea =
        new JTextArea(GuiActivator.getResources().getI18NString("service.gui.STATUS_MESSAGE_INFO"));

    JLabel infoTitleLabel =
        new JLabel(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE"));

    JPanel labelsPanel = new TransparentPanel(new GridLayout(0, 1));

    JButton okButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.OK"));

    JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT));

    this.setTitle(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE"));

    this.getRootPane().setDefaultButton(okButton);

    this.setPreferredSize(new Dimension(500, 200));

    infoArea.setEditable(false);
    infoArea.setLineWrap(true);
    infoArea.setWrapStyleWord(true);
    infoArea.setOpaque(false);

    dataPanel.add(messageLabel, BorderLayout.WEST);

    messageTextField.setText(presenceOpSet.getCurrentStatusMessage());
    dataPanel.add(messageTextField, BorderLayout.CENTER);

    infoTitleLabel.setHorizontalAlignment(JLabel.CENTER);
    infoTitleLabel.setFont(infoTitleLabel.getFont().deriveFont(Font.BOLD, 18.0f));

    labelsPanel.add(infoTitleLabel);
    labelsPanel.add(infoArea);
    labelsPanel.add(dataPanel);

    messagePanel = new TransparentPanel(new GridBagLayout());
    GridBagConstraints messagePanelConstraints = new GridBagConstraints();
    messagePanelConstraints.anchor = GridBagConstraints.NORTHWEST;
    messagePanelConstraints.fill = GridBagConstraints.NONE;
    messagePanelConstraints.gridx = 0;
    messagePanelConstraints.gridy = 0;
    messagePanelConstraints.insets = new Insets(5, 0, 5, 10);
    messagePanelConstraints.weightx = 0;
    messagePanelConstraints.weighty = 0;
    messagePanel.add(
        new ImageCanvas(ImageLoader.getImage(ImageLoader.RENAME_DIALOG_ICON)),
        messagePanelConstraints);

    messagePanelConstraints.anchor = GridBagConstraints.NORTH;
    messagePanelConstraints.fill = GridBagConstraints.HORIZONTAL;
    messagePanelConstraints.gridx = 1;
    messagePanelConstraints.insets = new Insets(0, 0, 0, 0);
    messagePanelConstraints.weightx = 1;
    messagePanel.add(labelsPanel, messagePanelConstraints);

    okButton.setName("ok");
    cancelButton.setName("cancel");

    okButton.setMnemonic(GuiActivator.getResources().getI18nMnemonic("service.gui.OK"));
    cancelButton.setMnemonic(GuiActivator.getResources().getI18nMnemonic("service.gui.CANCEL"));

    okButton.addActionListener(this);
    cancelButton.addActionListener(this);

    buttonsPanel.add(okButton);
    buttonsPanel.add(cancelButton);

    JPanel mainPanel = new TransparentPanel(new GridBagLayout());
    mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 0, 10));

    GridBagConstraints mainPanelConstraints = new GridBagConstraints();
    mainPanelConstraints.anchor = GridBagConstraints.NORTH;
    mainPanelConstraints.fill = GridBagConstraints.BOTH;
    mainPanelConstraints.gridx = 0;
    mainPanelConstraints.gridy = 0;
    mainPanelConstraints.weightx = 1;
    mainPanelConstraints.weighty = 1;
    mainPanel.add(messagePanel, mainPanelConstraints);

    mainPanelConstraints.anchor = GridBagConstraints.SOUTHEAST;
    mainPanelConstraints.fill = GridBagConstraints.NONE;
    mainPanelConstraints.gridy = 1;
    mainPanelConstraints.weightx = 0;
    mainPanelConstraints.weighty = 0;
    mainPanel.add(buttonsPanel, mainPanelConstraints);

    this.getContentPane().add(mainPanel);
  }

  /**
   * Handles the <tt>ActionEvent</tt>. In order to change the status message with the new one calls
   * the <tt>PublishStatusMessageThread</tt>.
   *
   * @param e the event that notified us of the action
   */
  public void actionPerformed(ActionEvent e) {
    JButton button = (JButton) e.getSource();
    String name = button.getName();

    if (name.equals("ok")) {
      new PublishStatusMessageThread(messageTextField.getText()).start();
    }

    this.dispose();
  }

  /** Requests the focus in the text field contained in this dialog. */
  public void requestFocusInField() {
    this.messageTextField.requestFocus();
  }

  /** This class allow to use a thread to change the presence status message. */
  private class PublishStatusMessageThread extends Thread {
    private String message;

    private PresenceStatus currentStatus;

    public PublishStatusMessageThread(String message) {
      this.message = message;

      this.currentStatus = presenceOpSet.getPresenceStatus();
    }

    public void run() {
      try {
        presenceOpSet.publishPresenceStatus(currentStatus, message);
      } catch (IllegalArgumentException e1) {

        logger.error("Error - changing status", e1);
      } catch (IllegalStateException e1) {

        logger.error("Error - changing status", e1);
      } catch (OperationFailedException e1) {

        if (e1.getErrorCode() == OperationFailedException.GENERAL_ERROR) {
          logger.error("General error occured while " + "publishing presence status.", e1);
        } else if (e1.getErrorCode() == OperationFailedException.NETWORK_FAILURE) {
          logger.error("Network failure occured while " + "publishing presence status.", e1);
        } else if (e1.getErrorCode() == OperationFailedException.PROVIDER_NOT_REGISTERED) {
          logger.error("Protocol provider must be" + "registered in order to change status.", e1);
        }
      }
    }
  }

  /**
   * Artificially clicks the cancel button when this panel is escaped.
   *
   * @param isEscaped indicates if this dialog is closed by the Esc shortcut
   */
  protected void close(boolean isEscaped) {
    if (isEscaped) cancelButton.doClick();
  }

  /** Reloads icon. */
  public void loadSkin() {
    if (messagePanel != null) {
      for (Component component : messagePanel.getComponents()) {
        if (component instanceof ImageCanvas) {
          ImageCanvas cmp = (ImageCanvas) component;
          cmp.setImage(ImageLoader.getImage(ImageLoader.RENAME_DIALOG_ICON));
        }
      }
    }
  }
}
/**
 * The <tt>OneToOneCallPeerPanel</tt> is the panel containing data for a call peer in a given call.
 * It contains information like call peer name, photo, call duration, etc.
 *
 * @author Yana Stamcheva
 * @author Lyubomir Marinov
 * @author Sebastien Vincent
 * @author Adam Netocny
 */
public class OneToOneCallPeerPanel extends TransparentPanel
    implements SwingCallPeerRenderer, PropertyChangeListener, Skinnable {
  /**
   * The <tt>Logger</tt> used by the <tt>OneToOneCallPeerPanel</tt> class and its instances for
   * logging output.
   */
  private static final Logger logger = Logger.getLogger(OneToOneCallPeerPanel.class);

  /** Serial version UID. */
  private static final long serialVersionUID = 0L;

  /** The <tt>CallPeer</tt>, which is rendered in this panel. */
  private final CallPeer callPeer;

  /** The <tt>Call</tt>, which is rendered in this panel. */
  private final Call call;

  /**
   * The <tt>CallPeerAdapter</tt> which implements common <tt>CallPeer</tt>-related listeners on
   * behalf of this instance.
   */
  private final CallPeerAdapter callPeerAdapter;

  /** The renderer of the call. */
  private final SwingCallRenderer callRenderer;

  /** The component showing the status of the underlying call peer. */
  private final JLabel callStatusLabel = new JLabel();

  /** The center component. */
  private final VideoContainer center;

  /**
   * The AWT <tt>Component</tt> which implements a button which allows closing/hiding the visual
   * <tt>Component</tt> which depicts the video streaming from the local peer/user to the remote
   * peer(s).
   */
  private Component closeLocalVisualComponentButton;

  /**
   * A listener to desktop sharing granted/revoked events and to mouse and keyboard interaction with
   * the remote video displaying the remote desktop.
   */
  private final DesktopSharingMouseAndKeyboardListener desktopSharingMouseAndKeyboardListener;

  /**
   * The indicator which determines whether {@link #dispose()} has already been invoked on this
   * instance. If <tt>true</tt>, this instance is considered non-functional and is to be left to the
   * garbage collector.
   */
  private boolean disposed = false;

  /** The DTMF label. */
  private final JLabel dtmfLabel = new JLabel();

  /** The component responsible for displaying an error message. */
  private JTextComponent errorMessageComponent;

  /** The label showing whether the call is on or off hold. */
  private final JLabel holdStatusLabel = new JLabel();

  /** Sound local level label. */
  private InputVolumeControlButton localLevel;

  /**
   * The <tt>Component</tt> which {@link #updateViewFromModelInEventDispatchThread()} last added to
   * {@link #center} as the visual <tt>Component</tt> displaying the video streaming from the local
   * peer/user to the remote peer(s).
   *
   * <p><b>Warning</b>: It is not to be used for any other purposes because it may not represent the
   * current state of the model of this view.
   */
  private Component localVideo;

  /** The label showing whether the voice has been set to mute. */
  private final JLabel muteStatusLabel = new JLabel();

  /** The <tt>Icon</tt> which represents the avatar of the associated call peer. */
  private ImageIcon peerImage;

  /** The name of the peer. */
  private String peerName;

  /** The label containing the user photo. */
  private final JLabel photoLabel;

  /** Sound remote level label. */
  private Component remoteLevel;

  /**
   * The <tt>Component</tt> which {@link #updateViewFromModelInEventDispatchThread()} last added to
   * {@link #center} as the visual <tt>Component</tt> displaying the video streaming from the remote
   * peer(s) to the local peer/user.
   *
   * <p><b>Warning</b>: It is not to be used for any other purposes because it may not represent the
   * current state of the model of this view.
   */
  private Component remoteVideo;

  /** Current id for security image. */
  private ImageID securityImageID = ImageLoader.SECURE_BUTTON_OFF;

  /** The panel containing security related components. */
  private SecurityPanel<?> securityPanel;

  /** The security status of the peer */
  private final SecurityStatusLabel securityStatusLabel = new SecurityStatusLabel();

  /** The status bar component. */
  private final Component statusBar;

  /** The facility which aids this instance in the dealing with the video-related information. */
  private final UIVideoHandler2 uiVideoHandler;

  /**
   * The <tt>Observer</tt> which listens to changes in the video-related information detected and
   * reported by {@link #uiVideoHandler}.
   */
  private final Observer uiVideoHandlerObserver =
      new Observer() {
        public void update(Observable o, Object arg) {
          updateViewFromModel();
        }
      };

  /**
   * The <tt>Runnable</tt> which is scheduled by {@link #updateViewFromModel()} for execution in the
   * AWT event dispatching thread in order to invoke {@link
   * #updateViewFromModelInEventDispatchThread()}.
   */
  private final Runnable updateViewFromModelInEventDispatchThread =
      new Runnable() {
        public void run() {
          /*
           * We receive events/notifications from various threads and we
           * respond to them in the AWT event dispatching thread. It is
           * possible to first schedule an event to be brought to the AWT
           * event dispatching thread, then to have #dispose() invoked on
           * this instance and, finally, to receive the scheduled event in
           * the AWT event dispatching thread. In such a case, this
           * disposed instance should not respond to the event.
           */
          if (!disposed) updateViewFromModelInEventDispatchThread();
        }
      };

  /**
   * Creates a <tt>CallPeerPanel</tt> for the given call peer.
   *
   * @param callRenderer the renderer of the call
   * @param callPeer the <tt>CallPeer</tt> represented in this panel
   * @param uiVideoHandler the facility which is to aid the new instance in the dealing with the
   *     video-related information
   */
  public OneToOneCallPeerPanel(
      SwingCallRenderer callRenderer, CallPeer callPeer, UIVideoHandler2 uiVideoHandler) {
    this.callRenderer = callRenderer;
    this.callPeer = callPeer;
    // we need to obtain call as soon as possible
    // cause if it fails too quickly we may fail to show it
    this.call = callPeer.getCall();
    this.uiVideoHandler = uiVideoHandler;

    peerName = CallManager.getPeerDisplayName(callPeer);
    securityPanel = SecurityPanel.create(this, callPeer, null);

    photoLabel = new JLabel(getPhotoLabelIcon());
    center = createCenter();
    statusBar = createStatusBar();

    setPeerImage(CallManager.getPeerImage(callPeer));

    /* Lay out the main Components of the UI. */
    setLayout(new GridBagLayout());

    GridBagConstraints cnstrnts = new GridBagConstraints();

    if (center != null) {
      cnstrnts.fill = GridBagConstraints.BOTH;
      cnstrnts.gridx = 0;
      cnstrnts.gridy = 1;
      cnstrnts.weightx = 1;
      cnstrnts.weighty = 1;
      add(center, cnstrnts);
    }
    if (statusBar != null) {
      cnstrnts.fill = GridBagConstraints.NONE;
      cnstrnts.gridx = 0;
      cnstrnts.gridy = 3;
      cnstrnts.weightx = 0;
      cnstrnts.weighty = 0;
      cnstrnts.insets = new Insets(5, 0, 0, 0);
      add(statusBar, cnstrnts);
    }

    createSoundLevelIndicators();
    initSecuritySettings();

    /*
     * Add the listeners which will be notified about changes in the model
     * and which will update this view.
     */
    callPeerAdapter = new CallPeerAdapter(callPeer, this);
    uiVideoHandler.addObserver(uiVideoHandlerObserver);

    /*
     * This view adapts to whether it is displayed in full-screen or
     * windowed mode.
     */
    if (callRenderer instanceof Component) {
      ((Component) callRenderer).addPropertyChangeListener(CallContainer.PROP_FULL_SCREEN, this);
    }

    OperationSetDesktopSharingClient desktopSharingClient =
        callPeer.getProtocolProvider().getOperationSet(OperationSetDesktopSharingClient.class);
    if (desktopSharingClient != null) {
      desktopSharingMouseAndKeyboardListener =
          new DesktopSharingMouseAndKeyboardListener(callPeer, desktopSharingClient);
    } else desktopSharingMouseAndKeyboardListener = null;

    updateViewFromModel();
  }

  /**
   * Creates the <tt>Component</tt> hierarchy of the central area of this <tt>CallPeerPanel</tt>
   * which displays the photo of the <tt>CallPeer</tt> or the video if any.
   */
  private VideoContainer createCenter() {
    photoLabel.setPreferredSize(new Dimension(90, 90));

    return createVideoContainer(photoLabel);
  }

  /** Creates sound level related components. */
  private void createSoundLevelIndicators() {
    TransparentPanel localLevelPanel = new TransparentPanel(new BorderLayout(5, 0));
    TransparentPanel remoteLevelPanel = new TransparentPanel(new BorderLayout(5, 0));

    localLevel =
        new InputVolumeControlButton(
            call, ImageLoader.MICROPHONE, ImageLoader.MUTE_BUTTON, false, false);
    remoteLevel =
        new OutputVolumeControlButton(call.getConference(), ImageLoader.HEADPHONE, false, false)
            .getComponent();

    final SoundLevelIndicator localLevelIndicator =
        new SoundLevelIndicator(SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL);
    final SoundLevelIndicator remoteLevelIndicator =
        new SoundLevelIndicator(SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL);

    localLevelPanel.add(localLevel, BorderLayout.WEST);
    localLevelPanel.add(localLevelIndicator, BorderLayout.CENTER);
    remoteLevelPanel.add(remoteLevel, BorderLayout.WEST);
    remoteLevelPanel.add(remoteLevelIndicator, BorderLayout.CENTER);

    GridBagConstraints constraints = new GridBagConstraints();
    constraints.fill = GridBagConstraints.NONE;
    constraints.gridx = 0;
    constraints.gridy = 5;
    constraints.weightx = 0;
    constraints.weighty = 0;
    constraints.insets = new Insets(10, 0, 0, 0);

    add(localLevelPanel, constraints);

    constraints.fill = GridBagConstraints.NONE;
    constraints.gridx = 0;
    constraints.gridy = 6;
    constraints.weightx = 0;
    constraints.weighty = 0;
    constraints.insets = new Insets(5, 0, 10, 0);

    add(remoteLevelPanel, constraints);

    if (!GuiActivator.getConfigurationService()
        .getBoolean(
            "net.java.sip.communicator.impl.gui.main.call." + "DISABLE_SOUND_LEVEL_INDICATORS",
            false)) {
      callPeer.addStreamSoundLevelListener(
          new SoundLevelListener() {
            public void soundLevelChanged(Object source, int level) {
              remoteLevelIndicator.updateSoundLevel(level);
            }
          });
      /*
       * By the time the UI gets to be initialized, the callPeer may have
       * been removed from its Call. As far as the UI is concerned, the
       * callPeer will never have a Call again and there will be no audio
       * levels to display anyway so there is no point in throwing a
       * NullPointerException here.
       */
      if (call != null) {
        call.addLocalUserSoundLevelListener(
            new SoundLevelListener() {
              public void soundLevelChanged(Object source, int level) {
                localLevelIndicator.updateSoundLevel(level);
              }
            });
      }
    }
  }

  /**
   * Creates the <tt>Component</tt> hierarchy of the area of status-related information such as
   * <tt>CallPeer</tt> display name, call duration, security status.
   *
   * @return the root of the <tt>Component</tt> hierarchy of the area of status-related information
   *     such as <tt>CallPeer</tt> display name, call duration, security status
   */
  private Component createStatusBar() {
    // stateLabel
    callStatusLabel.setForeground(Color.WHITE);
    dtmfLabel.setForeground(Color.WHITE);
    callStatusLabel.setText(callPeer.getState().getLocalizedStateString());

    PeerStatusPanel statusPanel = new PeerStatusPanel(new GridBagLayout());

    GridBagConstraints constraints = new GridBagConstraints();

    constraints.gridx = 0;
    constraints.gridy = 0;
    statusPanel.add(securityStatusLabel, constraints);
    initSecurityStatusLabel();

    constraints.gridx++;
    statusPanel.add(holdStatusLabel, constraints);

    constraints.gridx++;
    statusPanel.add(muteStatusLabel, constraints);

    constraints.gridx++;
    callStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 12));
    statusPanel.add(callStatusLabel, constraints);

    constraints.gridx++;
    constraints.weightx = 1f;
    statusPanel.add(dtmfLabel, constraints);

    return statusPanel;
  }

  /**
   * Creates a new AWT <tt>Container</tt> which can display a single <tt>Component</tt> at a time
   * (supposedly, one which represents video) and, in the absence of such a <tt>Component</tt>,
   * displays a predefined default <tt>Component</tt> (in accord with the previous supposition, one
   * which is the default when there is no video). The returned <tt>Container</tt> will track the
   * <tt>Components</tt>s added to and removed from it in order to make sure that
   * <tt>noVideoContainer</tt> is displayed as described.
   *
   * @param noVideoComponent the predefined default <tt>Component</tt> to be displayed in the
   *     returned <tt>Container</tt> when there is no other <tt>Component</tt> in it
   * @return a new <tt>Container</tt> which can display a single <tt>Component</tt> at a time and,
   *     in the absence of such a <tt>Component</tt>, displays <tt>noVideoComponent</tt>
   */
  private VideoContainer createVideoContainer(Component noVideoComponent) {
    Container oldParent = noVideoComponent.getParent();

    if (oldParent != null) oldParent.remove(noVideoComponent);

    return new VideoContainer(noVideoComponent, false);
  }

  /**
   * Releases the resources acquired by this instance which require explicit disposal (e.g. any
   * listeners added to the depicted <tt>CallPeer</tt>. Invoked by <tt>OneToOneCallPanel</tt> when
   * it determines that this <tt>OneToOneCallPeerPanel</tt> is no longer necessary.
   */
  public void dispose() {
    disposed = true;

    callPeerAdapter.dispose();
    uiVideoHandler.deleteObserver(uiVideoHandlerObserver);

    if (callRenderer instanceof Component) {
      ((Component) callRenderer).removePropertyChangeListener(CallContainer.PROP_FULL_SCREEN, this);
    }
  }

  /**
   * Returns the parent <tt>CallPanel</tt> containing this renderer.
   *
   * @return the parent <tt>CallPanel</tt> containing this renderer
   */
  public CallPanel getCallPanel() {
    return callRenderer.getCallContainer();
  }

  /**
   * Returns the parent call renderer.
   *
   * @return the parent call renderer
   */
  public CallRenderer getCallRenderer() {
    return callRenderer;
  }

  /**
   * Returns the component associated with this renderer.
   *
   * @return the component associated with this renderer
   */
  public Component getComponent() {
    return this;
  }

  /**
   * Returns the name of the peer, contained in this panel.
   *
   * @return the name of the peer, contained in this panel
   */
  public String getPeerName() {
    return peerName;
  }

  /**
   * Gets the <tt>Icon</tt> to be displayed in {@link #photoLabel}.
   *
   * @return the <tt>Icon</tt> to be displayed in {@link #photoLabel}
   */
  private ImageIcon getPhotoLabelIcon() {
    return (peerImage == null)
        ? new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO))
        : peerImage;
  }

  /** Initializes the security settings for this call peer. */
  private void initSecuritySettings() {
    CallPeerSecurityStatusEvent securityEvent = callPeer.getCurrentSecuritySettings();

    if (securityEvent instanceof CallPeerSecurityOnEvent)
      securityOn((CallPeerSecurityOnEvent) securityEvent);
  }

  /** Initializes the security status label, shown in the call status bar. */
  private void initSecurityStatusLabel() {
    securityStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));

    securityStatusLabel.addMouseListener(
        new MouseAdapter() {
          /** Invoked when a mouse button has been pressed on a component. */
          @Override
          public void mousePressed(MouseEvent e) {
            // Only show the security details if the security is on.
            SrtpControl ctrl = securityPanel.getSecurityControl();
            if (ctrl instanceof ZrtpControl && ctrl.getSecureCommunicationStatus()) {
              setSecurityPanelVisible(
                  !callRenderer
                      .getCallContainer()
                      .getCallWindow()
                      .getFrame()
                      .getGlassPane()
                      .isVisible());
            }
          }
        });
  }

  /**
   * Determines whether the visual <tt>Component</tt> depicting the video streaming from the local
   * peer/user to the remote peer(s) is currently visible.
   *
   * @return <tt>true</tt> if the visual <tt>Component</tt> depicting the video streaming from the
   *     local peer/user to the remote peer(s) is currently visible; otherwise, <tt>false</tt>
   */
  public boolean isLocalVideoVisible() {
    return uiVideoHandler.isLocalVideoVisible();
  }

  /** Reloads all icons. */
  public void loadSkin() {
    if (localLevel != null)
      localLevel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MICROPHONE)));

    if (remoteLevel != null && remoteLevel instanceof Skinnable)
      ((Skinnable) remoteLevel).loadSkin();

    if (muteStatusLabel.getIcon() != null)
      muteStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MUTE_STATUS_ICON)));

    if (holdStatusLabel.getIcon() != null)
      holdStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.HOLD_STATUS_ICON)));

    securityStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(securityImageID)));

    if (peerImage == null) {
      photoLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO)));
    }
  }

  /**
   * Prints the given DTMG character through this <tt>CallPeerRenderer</tt>.
   *
   * @param dtmfChar the DTMF char to print
   */
  public void printDTMFTone(char dtmfChar) {
    dtmfLabel.setText(dtmfLabel.getText() + dtmfChar);
    if (dtmfLabel.getBorder() == null)
      dtmfLabel.setBorder(BorderFactory.createEmptyBorder(2, 1, 2, 5));
  }

  /**
   * Notifies this instance about a change in the value of a property of a source which of interest
   * to this instance. For example, <tt>OneToOneCallPeerPanel</tt> updates its user
   * interface-related properties upon changes in the value of the {@link
   * CallContainer#PROP_FULL_SCREEN} property of its associated {@link #callRenderer}.
   *
   * @param ev a <tt>PropertyChangeEvent</tt> which identifies the source, the name of the property
   *     and the old and new values
   */
  public void propertyChange(PropertyChangeEvent ev) {
    if (CallContainer.PROP_FULL_SCREEN.equals(ev.getPropertyName())) updateViewFromModel();
  }

  /**
   * Re-dispatches glass pane mouse events only in case they occur on the security panel.
   *
   * @param glassPane the glass pane
   * @param e the mouse event in question
   */
  private void redispatchMouseEvent(Component glassPane, MouseEvent e) {
    Point glassPanePoint = e.getPoint();

    Point securityPanelPoint =
        SwingUtilities.convertPoint(glassPane, glassPanePoint, securityPanel);

    Component component;
    Point componentPoint;

    if (securityPanelPoint.y > 0) {
      component = securityPanel;
      componentPoint = securityPanelPoint;
    } else {
      Container contentPane =
          callRenderer.getCallContainer().getCallWindow().getFrame().getContentPane();

      Point containerPoint = SwingUtilities.convertPoint(glassPane, glassPanePoint, contentPane);

      component =
          SwingUtilities.getDeepestComponentAt(contentPane, containerPoint.x, containerPoint.y);

      componentPoint = SwingUtilities.convertPoint(contentPane, glassPanePoint, component);
    }

    if (component != null)
      component.dispatchEvent(
          new MouseEvent(
              component,
              e.getID(),
              e.getWhen(),
              e.getModifiers(),
              componentPoint.x,
              componentPoint.y,
              e.getClickCount(),
              e.isPopupTrigger()));

    e.consume();
  }

  /**
   * The handler for the security event received. The security event for starting establish a secure
   * connection.
   *
   * @param evt the security started event received
   */
  public void securityNegotiationStarted(CallPeerSecurityNegotiationStartedEvent evt) {
    if (Boolean.parseBoolean(
        GuiActivator.getResources().getSettingsString("impl.gui.PARANOIA_UI"))) {
      SrtpControl srtpControl = null;
      if (callPeer instanceof MediaAwareCallPeer) srtpControl = evt.getSecurityController();

      securityPanel = new ParanoiaTimerSecurityPanel<SrtpControl>(srtpControl);

      setSecurityPanelVisible(true);
    }
  }

  /**
   * Indicates that the security has gone off.
   *
   * @param evt the <tt>CallPeerSecurityOffEvent</tt> that notified us
   */
  public void securityOff(final CallPeerSecurityOffEvent evt) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              securityOff(evt);
            }
          });
      return;
    }

    if (evt.getSessionType() == CallPeerSecurityOffEvent.AUDIO_SESSION) {
      securityStatusLabel.setText("");
      securityStatusLabel.setSecurityOff();
      if (securityStatusLabel.getBorder() == null)
        securityStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 3));
    }

    securityPanel.securityOff(evt);
  }

  /**
   * Indicates that the security is turned on.
   *
   * <p>Sets the secured status icon to the status panel and initializes/updates the corresponding
   * security details.
   *
   * @param evt Details about the event that caused this message.
   */
  public void securityOn(final CallPeerSecurityOnEvent evt) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              securityOn(evt);
            }
          });
      return;
    }

    // If the securityOn is called without a specific event, we'll just set
    // the security label status to on.
    if (evt == null) {
      securityStatusLabel.setSecurityOn();
      return;
    }

    SrtpControl srtpControl = evt.getSecurityController();

    if (!srtpControl.requiresSecureSignalingTransport()
        || callPeer.getProtocolProvider().isSignalingTransportSecure()) {
      if (srtpControl instanceof ZrtpControl) {
        securityStatusLabel.setText("zrtp");

        if (!((ZrtpControl) srtpControl).isSecurityVerified())
          securityStatusLabel.setSecurityPending();
        else securityStatusLabel.setSecurityOn();
      } else securityStatusLabel.setSecurityOn();
    }

    // if we have some other panel, using other control
    if (!srtpControl.getClass().isInstance(securityPanel.getSecurityControl())
        || (securityPanel instanceof ParanoiaTimerSecurityPanel)) {
      setSecurityPanelVisible(false);

      securityPanel = SecurityPanel.create(this, callPeer, srtpControl);

      if (srtpControl instanceof ZrtpControl)
        ((ZrtpSecurityPanel) securityPanel).setSecurityStatusLabel(securityStatusLabel);
    }

    securityPanel.securityOn(evt);

    boolean isSecurityLowPriority =
        Boolean.parseBoolean(
            GuiActivator.getResources()
                .getSettingsString("impl.gui.I_DONT_CARE_THAT_MUCH_ABOUT_SECURITY"));

    // Display ZRTP panel in case SAS was not verified or a AOR mismtach
    // was detected during creation of ZrtpSecurityPanel.
    // Don't show panel if user does not care about security at all.
    if (srtpControl instanceof ZrtpControl
        && !isSecurityLowPriority
        && (!((ZrtpControl) srtpControl).isSecurityVerified()
            || ((ZrtpSecurityPanel) securityPanel).isZidAorMismatch())) {
      setSecurityPanelVisible(true);
    }

    this.revalidate();
  }

  /** Indicates that the security status is pending confirmation. */
  public void securityPending() {
    securityStatusLabel.setSecurityPending();
  }

  /**
   * Indicates that the security is timeouted, is not supported by the other end.
   *
   * @param evt Details about the event that caused this message.
   */
  public void securityTimeout(final CallPeerSecurityTimeoutEvent evt) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              securityTimeout(evt);
            }
          });
      return;
    }

    if (securityPanel != null) securityPanel.securityTimeout(evt);
  }

  /**
   * Sets the reason of a call failure if one occurs. The renderer should display this reason to the
   * user.
   *
   * @param reason the reason to display
   */
  public void setErrorReason(final String reason) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setErrorReason(reason);
            }
          });
      return;
    }

    if (errorMessageComponent == null) {
      errorMessageComponent = new JTextPane();

      JTextPane textPane = (JTextPane) errorMessageComponent;
      textPane.setEditable(false);
      textPane.setOpaque(false);

      StyledDocument doc = textPane.getStyledDocument();

      MutableAttributeSet standard = new SimpleAttributeSet();
      StyleConstants.setAlignment(standard, StyleConstants.ALIGN_CENTER);
      StyleConstants.setFontFamily(standard, textPane.getFont().getFamily());
      StyleConstants.setFontSize(standard, 12);
      doc.setParagraphAttributes(0, 0, standard, true);

      GridBagConstraints constraints = new GridBagConstraints();
      constraints.fill = GridBagConstraints.HORIZONTAL;
      constraints.gridx = 0;
      constraints.gridy = 4;
      constraints.weightx = 1;
      constraints.weighty = 0;
      constraints.insets = new Insets(5, 0, 0, 0);

      add(errorMessageComponent, constraints);
      this.revalidate();
    }

    errorMessageComponent.setText(reason);

    if (isVisible()) errorMessageComponent.repaint();
  }

  /**
   * Shows/hides the visual <tt>Component</tt> depicting the video streaming from the local
   * peer/user to the remote peer(s).
   *
   * @param visible <tt>true</tt> to show the visual <tt>Component</tt> depicting the video
   *     streaming from the local peer/user to the remote peer(s); <tt>false</tt>, otherwise
   */
  public void setLocalVideoVisible(boolean visible) {
    uiVideoHandler.setLocalVideoVisible(visible);
  }

  /**
   * Sets the mute status icon to the status panel.
   *
   * @param isMute indicates if the call with this peer is muted
   */
  public void setMute(final boolean isMute) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setMute(isMute);
            }
          });
      return;
    }

    if (isMute) {
      muteStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MUTE_STATUS_ICON)));
      muteStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3));
    } else {
      muteStatusLabel.setIcon(null);
      muteStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
    }

    // Update input volume control button state to reflect the current
    // mute status.
    if (localLevel.isSelected() != isMute) localLevel.setSelected(isMute);

    this.revalidate();
    this.repaint();
  }

  /**
   * Sets the "on hold" property value.
   *
   * @param isOnHold indicates if the call with this peer is put on hold
   */
  public void setOnHold(final boolean isOnHold) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setOnHold(isOnHold);
            }
          });
      return;
    }

    if (isOnHold) {
      holdStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.HOLD_STATUS_ICON)));
      holdStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3));
    } else {
      holdStatusLabel.setIcon(null);
      holdStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
    }

    this.revalidate();
    this.repaint();
  }

  /**
   * Set the image of the peer
   *
   * @param image new image
   */
  public void setPeerImage(byte[] image) {
    // If the image is still null we try to obtain it from one of the
    // available contact sources.
    if (image == null || image.length <= 0) {
      GuiActivator.getContactList().setSourceContactImage(peerName, photoLabel, 100, 100);
    } else {
      peerImage = ImageUtils.getScaledRoundedIcon(image, 100, 100);
      if (peerImage == null) peerImage = getPhotoLabelIcon();

      if (!SwingUtilities.isEventDispatchThread()) {
        SwingUtilities.invokeLater(
            new Runnable() {
              public void run() {
                photoLabel.setIcon(peerImage);
                photoLabel.repaint();
              }
            });
      } else {
        photoLabel.setIcon(peerImage);
        photoLabel.repaint();
      }
    }
  }

  /**
   * Sets the name of the peer.
   *
   * @param name the name of the peer
   */
  public void setPeerName(final String name) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setPeerName(name);
            }
          });
      return;
    }

    peerName = name;

    ((OneToOneCallPanel) callRenderer).setPeerName(name);
  }

  /**
   * Sets the state of the contained call peer by specifying the state name.
   *
   * @param oldState the previous state of the peer
   * @param newState the new state of the peer
   * @param stateString the state of the contained call peer
   */
  public void setPeerState(
      final CallPeerState oldState, final CallPeerState newState, final String stateString) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setPeerState(oldState, newState, stateString);
            }
          });
      return;
    }

    this.callStatusLabel.setText(stateString);

    if (newState == CallPeerState.CONNECTED
        && !CallPeerState.isOnHold(oldState)
        && !securityStatusLabel.isSecurityStatusSet()) {
      securityStatusLabel.setSecurityOff();
    }
  }

  /**
   * Shows/hides the security panel.
   *
   * @param isVisible <tt>true</tt> to show the security panel, <tt>false</tt> to hide it
   */
  public void setSecurityPanelVisible(final boolean isVisible) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setSecurityPanelVisible(isVisible);
            }
          });
      return;
    }

    final JFrame callFrame = callRenderer.getCallContainer().getCallWindow().getFrame();

    final JPanel glassPane = (JPanel) callFrame.getGlassPane();

    if (!isVisible) {
      // Need to hide the security panel explicitly in order to keep the
      // fade effect.
      securityPanel.setVisible(false);
      glassPane.setVisible(false);
      glassPane.removeAll();
    } else {
      glassPane.setLayout(null);
      glassPane.addMouseListener(
          new MouseListener() {
            public void mouseClicked(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mouseEntered(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mouseExited(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mousePressed(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mouseReleased(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }
          });

      Point securityLabelPoint = securityStatusLabel.getLocation();

      Point newPoint =
          SwingUtilities.convertPoint(
              securityStatusLabel.getParent(),
              securityLabelPoint.x,
              securityLabelPoint.y,
              callFrame);

      securityPanel.setBeginPoint(new Point((int) newPoint.getX() + 15, 0));
      securityPanel.setBounds(0, (int) newPoint.getY() - 5, this.getWidth(), 130);

      glassPane.add(securityPanel);
      // Need to show the security panel explicitly in order to keep the
      // fade effect.
      securityPanel.setVisible(true);
      glassPane.setVisible(true);

      glassPane.addComponentListener(
          new ComponentAdapter() {
            /** Invoked when the component's size changes. */
            @Override
            public void componentResized(ComponentEvent e) {
              if (glassPane.isVisible()) {
                glassPane.setVisible(false);
                callFrame.removeComponentListener(this);
              }
            }
          });
    }
  }

  /**
   * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of
   * its model i.e. <tt>callPeer</tt>.
   */
  private void updateViewFromModel() {
    /*
     * We receive events/notifications from various threads and we respond
     * to them in the AWT event dispatching thread. It is possible to first
     * schedule an event to be brought to the AWT event dispatching thread,
     * then to have #dispose() invoked on this instance and, finally, to
     * receive the scheduled event in the AWT event dispatching thread. In
     * such a case, this disposed instance should not respond to the event
     * because it may, for example, steal a visual Components depicting
     * video (which cannot belong to more than one parent at a time) from
     * another non-disposed OneToOneCallPeerPanel.
     */
    if (!disposed) {
      if (SwingUtilities.isEventDispatchThread()) updateViewFromModelInEventDispatchThread();
      else {
        SwingUtilities.invokeLater(updateViewFromModelInEventDispatchThread);
      }
    }
  }

  /**
   * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of
   * its model i.e. <tt>callPeer</tt>. The update is performed in the AWT event dispatching thread.
   */
  private void updateViewFromModelInEventDispatchThread() {
    /*
     * We receive events/notifications from various threads and we respond
     * to them in the AWT event dispatching thread. It is possible to first
     * schedule an event to be brought to the AWT event dispatching thread,
     * then to have #dispose() invoked on this instance and, finally, to
     * receive the scheduled event in the AWT event dispatching thread. In
     * such a case, this disposed instance should not respond to the event
     * because it may, for example, steal a visual Components depicting
     * video (which cannot belong to more than one parent at a time) from
     * another non-disposed OneToOneCallPeerPanel.
     */
    if (disposed) return;

    /*
     * Update the display of visual <tt>Component</tt>s depicting video
     * streaming between the local peer/user and the remote peer(s).
     */

    OperationSetVideoTelephony videoTelephony =
        callPeer.getProtocolProvider().getOperationSet(OperationSetVideoTelephony.class);
    Component remoteVideo = null;
    Component localVideo = null;

    if (videoTelephony != null) {
      List<Component> remoteVideos = videoTelephony.getVisualComponents(callPeer);

      if ((remoteVideos != null) && !remoteVideos.isEmpty()) {
        /*
         * TODO OneToOneCallPeerPanel displays a one-to-one conversation
         * between the local peer/user and a specific remote peer. If
         * the remote peer is the focus of a telephony conference of its
         * own, it may be sending multiple videos to the local peer.
         * Switching to a user interface which displays multiple videos
         * is the responsibility of whoever decided that this
         * OneToOneCallPeerPanel is to be used to depict the current
         * state of the CallConference associated with the CallPeer
         * depicted by this instance. If that switching decides that
         * this instance is to continue being the user interface, then
         * we should probably pick up the remote video which is
         * generated by the remote peer and not one of its
         * ConferenceMembers.
         */
        remoteVideo = remoteVideos.get(0);
      }

      if (uiVideoHandler.isLocalVideoVisible()) {
        try {
          localVideo = videoTelephony.getLocalVisualComponent(callPeer);
        } catch (OperationFailedException ofe) {
          /*
           * Well, we cannot do much about the exception. We'll just
           * not display the local video.
           */
          logger.warn("Failed to retrieve local video to be displayed.", ofe);
        }
      }

      /*
       * Determine whether there is actually a change in the local and
       * remote videos which requires an update.
       */
      boolean localVideoChanged =
          ((localVideo != this.localVideo)
              || ((localVideo != null) && !UIVideoHandler2.isAncestor(center, localVideo)));
      boolean remoteVideoChanged =
          ((remoteVideo != this.remoteVideo)
              || ((remoteVideo != null) && !UIVideoHandler2.isAncestor(center, remoteVideo)));

      // If the remote video has changed, maybe the CallPanel can display
      // the LO/SD/HD button.
      if (remoteVideoChanged) {
        // Updates video component which may listen the mouse and key
        // events.
        if (desktopSharingMouseAndKeyboardListener != null) {
          desktopSharingMouseAndKeyboardListener.setVideoComponent(remoteVideo);
        }

        CallPanel callPanel = callRenderer.getCallContainer();
        // The remote video has been added, then tries to display the
        // LO/SD/HD button.
        if (remoteVideo != null) {
          callPanel.addRemoteVideoSpecificComponents(callPeer);
        }
        // The remote video has been removed, then hide the LO/SD/HD
        // button if it is currently displayed.
        else {
          callPanel.removeRemoteVideoSpecificComponents();
        }
      }

      if (localVideoChanged || remoteVideoChanged) {
        /*
         * VideoContainer and JAWTRenderer cannot handle random
         * additions of Components. Removing the localVideo when the
         * user has requests its hiding though, should work without
         * removing all Components from the VideoCotainer and adding
         * them again.
         */
        if (localVideoChanged && !remoteVideoChanged && (localVideo == null)) {
          if (this.localVideo != null) {
            center.remove(this.localVideo);
            this.localVideo = null;

            if (closeLocalVisualComponentButton != null)
              center.remove(closeLocalVisualComponentButton);
          }
        } else {
          center.removeAll();
          this.localVideo = null;
          this.remoteVideo = null;

          /*
           * AWT does not make a guarantee about the Z order even
           * within an operating system i.e. the order of adding the
           * Components to their Container does not mean that they
           * will be determinedly painted in that or reverse order.
           * Anyway, there appears to be an expectation among the
           * developers less acquainted with AWT that AWT paints the
           * Components of a Container in an order that is the reverse
           * of the order of their adding. In order to satisfy that
           * expectation and thus give at least some idea to the
           * developers reading the code bellow, do add the Components
           * according to that expectation.
           */

          if (localVideo != null) {
            if (closeLocalVisualComponentButton == null) {
              closeLocalVisualComponentButton = new CloseLocalVisualComponentButton(uiVideoHandler);
            }
            center.add(closeLocalVisualComponentButton, VideoLayout.CLOSE_LOCAL_BUTTON, -1);

            center.add(localVideo, VideoLayout.LOCAL, -1);
            this.localVideo = localVideo;
          }

          if (remoteVideo != null) {
            center.add(remoteVideo, VideoLayout.CENTER_REMOTE, -1);
            this.remoteVideo = remoteVideo;
          }
        }
      }
    }
  }

  /** The <tt>TransparentPanel</tt> that will display the peer status. */
  private static class PeerStatusPanel extends TransparentPanel {
    /**
     * Silence the serial warning. Though there isn't a plan to serialize the instances of the
     * class, there're no fields so the default serialization routine will work.
     */
    private static final long serialVersionUID = 0L;

    /**
     * Constructs a new <tt>PeerStatusPanel</tt>.
     *
     * @param layout the <tt>LayoutManager</tt> to use
     */
    public PeerStatusPanel(LayoutManager layout) {
      super(layout);
    }

    /** @{inheritDoc} */
    @Override
    public void paintComponent(Graphics g) {
      super.paintComponent(g);

      g = g.create();

      try {
        AntialiasingManager.activateAntialiasing(g);

        g.setColor(Color.DARK_GRAY);
        g.fillRoundRect(0, 0, this.getWidth(), this.getHeight(), 10, 10);
      } finally {
        g.dispose();
      }
    }
  }
}
  /**
   * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of
   * its model i.e. <tt>callPeer</tt>. The update is performed in the AWT event dispatching thread.
   */
  private void updateViewFromModelInEventDispatchThread() {
    /*
     * We receive events/notifications from various threads and we respond
     * to them in the AWT event dispatching thread. It is possible to first
     * schedule an event to be brought to the AWT event dispatching thread,
     * then to have #dispose() invoked on this instance and, finally, to
     * receive the scheduled event in the AWT event dispatching thread. In
     * such a case, this disposed instance should not respond to the event
     * because it may, for example, steal a visual Components depicting
     * video (which cannot belong to more than one parent at a time) from
     * another non-disposed OneToOneCallPeerPanel.
     */
    if (disposed) return;

    /*
     * Update the display of visual <tt>Component</tt>s depicting video
     * streaming between the local peer/user and the remote peer(s).
     */

    OperationSetVideoTelephony videoTelephony =
        callPeer.getProtocolProvider().getOperationSet(OperationSetVideoTelephony.class);
    Component remoteVideo = null;
    Component localVideo = null;

    if (videoTelephony != null) {
      List<Component> remoteVideos = videoTelephony.getVisualComponents(callPeer);

      if ((remoteVideos != null) && !remoteVideos.isEmpty()) {
        /*
         * TODO OneToOneCallPeerPanel displays a one-to-one conversation
         * between the local peer/user and a specific remote peer. If
         * the remote peer is the focus of a telephony conference of its
         * own, it may be sending multiple videos to the local peer.
         * Switching to a user interface which displays multiple videos
         * is the responsibility of whoever decided that this
         * OneToOneCallPeerPanel is to be used to depict the current
         * state of the CallConference associated with the CallPeer
         * depicted by this instance. If that switching decides that
         * this instance is to continue being the user interface, then
         * we should probably pick up the remote video which is
         * generated by the remote peer and not one of its
         * ConferenceMembers.
         */
        remoteVideo = remoteVideos.get(0);
      }

      if (uiVideoHandler.isLocalVideoVisible()) {
        try {
          localVideo = videoTelephony.getLocalVisualComponent(callPeer);
        } catch (OperationFailedException ofe) {
          /*
           * Well, we cannot do much about the exception. We'll just
           * not display the local video.
           */
          logger.warn("Failed to retrieve local video to be displayed.", ofe);
        }
      }

      /*
       * Determine whether there is actually a change in the local and
       * remote videos which requires an update.
       */
      boolean localVideoChanged =
          ((localVideo != this.localVideo)
              || ((localVideo != null) && !UIVideoHandler2.isAncestor(center, localVideo)));
      boolean remoteVideoChanged =
          ((remoteVideo != this.remoteVideo)
              || ((remoteVideo != null) && !UIVideoHandler2.isAncestor(center, remoteVideo)));

      // If the remote video has changed, maybe the CallPanel can display
      // the LO/SD/HD button.
      if (remoteVideoChanged) {
        // Updates video component which may listen the mouse and key
        // events.
        if (desktopSharingMouseAndKeyboardListener != null) {
          desktopSharingMouseAndKeyboardListener.setVideoComponent(remoteVideo);
        }

        CallPanel callPanel = callRenderer.getCallContainer();
        // The remote video has been added, then tries to display the
        // LO/SD/HD button.
        if (remoteVideo != null) {
          callPanel.addRemoteVideoSpecificComponents(callPeer);
        }
        // The remote video has been removed, then hide the LO/SD/HD
        // button if it is currently displayed.
        else {
          callPanel.removeRemoteVideoSpecificComponents();
        }
      }

      if (localVideoChanged || remoteVideoChanged) {
        /*
         * VideoContainer and JAWTRenderer cannot handle random
         * additions of Components. Removing the localVideo when the
         * user has requests its hiding though, should work without
         * removing all Components from the VideoCotainer and adding
         * them again.
         */
        if (localVideoChanged && !remoteVideoChanged && (localVideo == null)) {
          if (this.localVideo != null) {
            center.remove(this.localVideo);
            this.localVideo = null;

            if (closeLocalVisualComponentButton != null)
              center.remove(closeLocalVisualComponentButton);
          }
        } else {
          center.removeAll();
          this.localVideo = null;
          this.remoteVideo = null;

          /*
           * AWT does not make a guarantee about the Z order even
           * within an operating system i.e. the order of adding the
           * Components to their Container does not mean that they
           * will be determinedly painted in that or reverse order.
           * Anyway, there appears to be an expectation among the
           * developers less acquainted with AWT that AWT paints the
           * Components of a Container in an order that is the reverse
           * of the order of their adding. In order to satisfy that
           * expectation and thus give at least some idea to the
           * developers reading the code bellow, do add the Components
           * according to that expectation.
           */

          if (localVideo != null) {
            if (closeLocalVisualComponentButton == null) {
              closeLocalVisualComponentButton = new CloseLocalVisualComponentButton(uiVideoHandler);
            }
            center.add(closeLocalVisualComponentButton, VideoLayout.CLOSE_LOCAL_BUTTON, -1);

            center.add(localVideo, VideoLayout.LOCAL, -1);
            this.localVideo = localVideo;
          }

          if (remoteVideo != null) {
            center.add(remoteVideo, VideoLayout.CENTER_REMOTE, -1);
            this.remoteVideo = remoteVideo;
          }
        }
      }
    }
  }
Exemple #6
0
/**
 * The <tt>CallManager</tt> is the one that handles calls. It contains also the "Call" and "Hangup"
 * buttons panel. Here are handles incoming and outgoing calls from and to the call operation set.
 *
 * @author Yana Stamcheva
 */
public class CallManager extends JPanel
    implements ActionListener, CallListener, ListSelectionListener, ChangeListener {
  private Logger logger = Logger.getLogger(CallManager.class.getName());

  private CallComboBox phoneNumberCombo;

  private JPanel comboPanel = new JPanel(new BorderLayout());

  private JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));

  private JLabel callViaLabel = new JLabel(Messages.getI18NString("callVia").getText() + " ");

  private JPanel callViaPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 4));

  private AccountSelectorBox accountSelectorBox;

  private SIPCommButton callButton =
      new SIPCommButton(
          ImageLoader.getImage(ImageLoader.CALL_BUTTON_BG),
          ImageLoader.getImage(ImageLoader.CALL_ROLLOVER_BUTTON_BG),
          null,
          ImageLoader.getImage(ImageLoader.CALL_BUTTON_PRESSED_BG));

  private SIPCommButton hangupButton =
      new SIPCommButton(
          ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_BG),
          ImageLoader.getImage(ImageLoader.HANGUP_ROLLOVER_BUTTON_BG),
          null,
          ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_PRESSED_BG));

  private SIPCommButton minimizeButton =
      new SIPCommButton(
          ImageLoader.getImage(ImageLoader.CALL_PANEL_MINIMIZE_BUTTON),
          ImageLoader.getImage(ImageLoader.CALL_PANEL_MINIMIZE_ROLLOVER_BUTTON));

  private SIPCommButton restoreButton =
      new SIPCommButton(
          ImageLoader.getImage(ImageLoader.CALL_PANEL_RESTORE_BUTTON),
          ImageLoader.getImage(ImageLoader.CALL_PANEL_RESTORE_ROLLOVER_BUTTON));

  private JPanel minimizeButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));

  private MainFrame mainFrame;

  private Hashtable activeCalls = new Hashtable();

  private boolean isCallMetaContact;

  private Hashtable removeCallTimers = new Hashtable();

  private ProtocolProviderService selectedCallProvider;

  /**
   * Creates an instance of <tt>CallManager</tt>.
   *
   * @param mainFrame The main application window.
   */
  public CallManager(MainFrame mainFrame) {
    super(new BorderLayout());

    this.mainFrame = mainFrame;

    this.phoneNumberCombo = new CallComboBox(this);

    this.accountSelectorBox = new AccountSelectorBox(this);

    this.buttonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));

    this.comboPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 0, 5));

    this.init();
  }

  /** Initializes and constructs this panel. */
  private void init() {
    this.phoneNumberCombo.setEditable(true);

    this.callViaPanel.add(callViaLabel);
    this.callViaPanel.add(accountSelectorBox);

    this.comboPanel.add(phoneNumberCombo, BorderLayout.CENTER);

    this.callButton.setName("call");
    this.hangupButton.setName("hangup");
    this.minimizeButton.setName("minimize");
    this.restoreButton.setName("restore");

    this.minimizeButton.setToolTipText(
        Messages.getI18NString("hideCallPanel").getText() + " Ctrl - H");
    this.restoreButton.setToolTipText(
        Messages.getI18NString("showCallPanel").getText() + " Ctrl - H");

    this.callButton.addActionListener(this);
    this.hangupButton.addActionListener(this);
    this.minimizeButton.addActionListener(this);
    this.restoreButton.addActionListener(this);

    this.buttonsPanel.add(callButton);
    this.buttonsPanel.add(hangupButton);

    this.callButton.setEnabled(false);

    this.hangupButton.setEnabled(false);

    this.add(minimizeButtonPanel, BorderLayout.SOUTH);

    this.setCallPanelVisible(ConfigurationManager.isCallPanelShown());
  }

  /**
   * Handles the <tt>ActionEvent</tt> generated when user presses one of the buttons in this panel.
   */
  public void actionPerformed(ActionEvent evt) {
    JButton button = (JButton) evt.getSource();
    String buttonName = button.getName();

    if (buttonName.equals("call")) {
      Component selectedPanel = mainFrame.getSelectedTab();

      // call button is pressed over an already open call panel
      if (selectedPanel != null
          && selectedPanel instanceof CallPanel
          && ((CallPanel) selectedPanel).getCall().getCallState()
              == CallState.CALL_INITIALIZATION) {

        NotificationManager.stopSound(NotificationManager.BUSY_CALL);
        NotificationManager.stopSound(NotificationManager.INCOMING_CALL);

        CallPanel callPanel = (CallPanel) selectedPanel;

        Iterator participantPanels = callPanel.getParticipantsPanels();

        while (participantPanels.hasNext()) {
          CallParticipantPanel panel = (CallParticipantPanel) participantPanels.next();

          panel.setState("Connecting");
        }

        Call call = callPanel.getCall();

        answerCall(call);
      }
      // call button is pressed over the call list
      else if (selectedPanel != null
          && selectedPanel instanceof CallListPanel
          && ((CallListPanel) selectedPanel).getCallList().getSelectedIndex() != -1) {

        CallListPanel callListPanel = (CallListPanel) selectedPanel;

        GuiCallParticipantRecord callRecord =
            (GuiCallParticipantRecord) callListPanel.getCallList().getSelectedValue();

        String stringContact = callRecord.getParticipantName();

        createCall(stringContact);
      }
      // call button is pressed over the contact list
      else if (selectedPanel != null && selectedPanel instanceof ContactListPanel) {
        // call button is pressed when a meta contact is selected
        if (isCallMetaContact) {
          Object[] selectedContacts =
              mainFrame.getContactListPanel().getContactList().getSelectedValues();

          Vector telephonyContacts = new Vector();

          for (int i = 0; i < selectedContacts.length; i++) {

            Object o = selectedContacts[i];

            if (o instanceof MetaContact) {

              Contact contact =
                  ((MetaContact) o).getDefaultContact(OperationSetBasicTelephony.class);

              if (contact != null) telephonyContacts.add(contact);
              else {
                new ErrorDialog(
                        this.mainFrame,
                        Messages.getI18NString("warning").getText(),
                        Messages.getI18NString(
                                "contactNotSupportingTelephony",
                                new String[] {((MetaContact) o).getDisplayName()})
                            .getText())
                    .showDialog();
              }
            }
          }

          if (telephonyContacts.size() > 0) createCall(telephonyContacts);

        } else if (!phoneNumberCombo.isComboFieldEmpty()) {

          // if no contact is selected checks if the user has chosen
          // or has
          // writen something in the phone combo box

          String stringContact = phoneNumberCombo.getEditor().getItem().toString();

          createCall(stringContact);
        }
      } else if (selectedPanel != null && selectedPanel instanceof DialPanel) {
        String stringContact = phoneNumberCombo.getEditor().getItem().toString();
        createCall(stringContact);
      }
    } else if (buttonName.equalsIgnoreCase("hangup")) {
      Component selectedPanel = this.mainFrame.getSelectedTab();

      if (selectedPanel != null && selectedPanel instanceof CallPanel) {

        NotificationManager.stopSound(NotificationManager.BUSY_CALL);
        NotificationManager.stopSound(NotificationManager.INCOMING_CALL);
        NotificationManager.stopSound(NotificationManager.OUTGOING_CALL);

        CallPanel callPanel = (CallPanel) selectedPanel;

        Call call = callPanel.getCall();

        if (removeCallTimers.containsKey(callPanel)) {
          ((Timer) removeCallTimers.get(callPanel)).stop();
          removeCallTimers.remove(callPanel);
        }

        removeCallPanel(callPanel);

        if (call != null) {
          ProtocolProviderService pps = call.getProtocolProvider();

          OperationSetBasicTelephony telephony = mainFrame.getTelephonyOpSet(pps);

          Iterator participants = call.getCallParticipants();

          while (participants.hasNext()) {
            try {
              // now we hang up the first call participant in the
              // call
              telephony.hangupCallParticipant((CallParticipant) participants.next());
            } catch (OperationFailedException e) {
              logger.error("Hang up was not successful: " + e);
            }
          }
        }
      }
    } else if (buttonName.equalsIgnoreCase("minimize")) {
      JCheckBoxMenuItem hideCallPanelItem =
          mainFrame.getMainMenu().getViewMenu().getHideCallPanelItem();

      if (!hideCallPanelItem.isSelected()) hideCallPanelItem.setSelected(true);

      this.setCallPanelVisible(false);
    } else if (buttonName.equalsIgnoreCase("restore")) {

      JCheckBoxMenuItem hideCallPanelItem =
          mainFrame.getMainMenu().getViewMenu().getHideCallPanelItem();

      if (hideCallPanelItem.isSelected()) hideCallPanelItem.setSelected(false);

      this.setCallPanelVisible(true);
    }
  }

  /** Hides the panel containing call and hangup buttons. */
  public void setCallPanelVisible(boolean isVisible) {
    if (isVisible) {
      this.add(comboPanel, BorderLayout.NORTH);
      this.add(buttonsPanel, BorderLayout.CENTER);

      this.minimizeButtonPanel.removeAll();
      this.minimizeButtonPanel.add(minimizeButton);
    } else {
      this.remove(comboPanel);
      this.remove(buttonsPanel);

      this.minimizeButtonPanel.removeAll();
      this.minimizeButtonPanel.add(restoreButton);

      if (mainFrame.isVisible())
        this.mainFrame.getContactListPanel().getContactList().requestFocus();
    }

    if (ConfigurationManager.isCallPanelShown() != isVisible)
      ConfigurationManager.setShowCallPanel(isVisible);

    this.mainFrame.validate();
  }

  /**
   * Adds the given call account to the list of call via accounts.
   *
   * @param pps the protocol provider service corresponding to the account
   */
  public void addCallAccount(ProtocolProviderService pps) {
    if (accountSelectorBox.getAccountsNumber() > 0) {
      this.comboPanel.add(callViaPanel, BorderLayout.SOUTH);
    }
    accountSelectorBox.addAccount(pps);
  }

  /**
   * Removes the account corresponding to the given protocol provider from the call via selector
   * box.
   *
   * @param pps the protocol provider service to remove
   */
  public void removeCallAccount(ProtocolProviderService pps) {
    this.accountSelectorBox.removeAccount(pps);

    if (accountSelectorBox.getAccountsNumber() < 2) {
      this.comboPanel.remove(callViaPanel);
    }
  }

  /**
   * Returns TRUE if the account corresponding to the given protocol provider is already contained
   * in the call via selector box, otherwise returns FALSE.
   *
   * @param pps the protocol provider service for the account
   * @return TRUE if the account corresponding to the given protocol provider is already contained
   *     in the call via selector box, otherwise returns FALSE
   */
  public boolean containsCallAccount(ProtocolProviderService pps) {
    return accountSelectorBox.containsAccount(pps);
  }

  /**
   * Updates the call via account status.
   *
   * @param pps the protocol provider service for the account
   */
  public void updateCallAccountStatus(ProtocolProviderService pps) {
    accountSelectorBox.updateAccountStatus(pps);
  }

  /**
   * Returns the account selector box.
   *
   * @return the account selector box.
   */
  public AccountSelectorBox getAccountSelectorBox() {
    return accountSelectorBox;
  }

  /**
   * Sets the protocol provider to use for a call.
   *
   * @param provider the protocol provider to use for a call.
   */
  public void setCallProvider(ProtocolProviderService provider) {
    this.selectedCallProvider = provider;
  }

  /**
   * Implements CallListener.incomingCallReceived. When a call is received creates a call panel and
   * adds it to the main tabbed pane and plays the ring phone sound to the user.
   */
  public void incomingCallReceived(CallEvent event) {
    Call sourceCall = event.getSourceCall();

    CallPanel callPanel = new CallPanel(this, sourceCall, GuiCallParticipantRecord.INCOMING_CALL);

    mainFrame.addCallPanel(callPanel);

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

    if (!mainFrame.isVisible()) mainFrame.setVisible(true);

    mainFrame.toFront();

    this.callButton.setEnabled(true);
    this.hangupButton.setEnabled(true);

    NotificationManager.fireNotification(
        NotificationManager.INCOMING_CALL,
        null,
        "Incoming call recived from: " + sourceCall.getCallParticipants().next());

    activeCalls.put(sourceCall, callPanel);

    this.setCallPanelVisible(true);
  }

  /**
   * Implements CallListener.callEnded. Stops sounds that are playing at the moment if there're any.
   * Removes the call panel and disables the hangup button.
   */
  public void callEnded(CallEvent event) {
    Call sourceCall = event.getSourceCall();

    NotificationManager.stopSound(NotificationManager.BUSY_CALL);
    NotificationManager.stopSound(NotificationManager.INCOMING_CALL);
    NotificationManager.stopSound(NotificationManager.OUTGOING_CALL);

    if (activeCalls.get(sourceCall) != null) {
      CallPanel callPanel = (CallPanel) activeCalls.get(sourceCall);

      this.removeCallPanelWait(callPanel);
    }
  }

  public void outgoingCallCreated(CallEvent event) {}

  /**
   * Removes the given call panel tab.
   *
   * @param callPanel the CallPanel to remove
   */
  public void removeCallPanelWait(CallPanel callPanel) {
    Timer timer = new Timer(5000, new RemoveCallPanelListener(callPanel));

    this.removeCallTimers.put(callPanel, timer);

    timer.setRepeats(false);
    timer.start();
  }

  /**
   * Removes the given call panel tab.
   *
   * @param callPanel the CallPanel to remove
   */
  private void removeCallPanel(CallPanel callPanel) {
    if (callPanel.getCall() != null && activeCalls.contains(callPanel.getCall())) {
      this.activeCalls.remove(callPanel.getCall());
    }

    mainFrame.removeCallPanel(callPanel);
    updateButtonsStateAccordingToSelectedPanel();
  }

  /** Removes the given CallPanel from the main tabbed pane. */
  private class RemoveCallPanelListener implements ActionListener {
    private CallPanel callPanel;

    public RemoveCallPanelListener(CallPanel callPanel) {
      this.callPanel = callPanel;
    }

    public void actionPerformed(ActionEvent e) {
      removeCallPanel(callPanel);
    }
  }

  /**
   * Implements ListSelectionListener.valueChanged. Enables or disables call and hangup buttons
   * depending on the selection in the contactlist.
   */
  public void valueChanged(ListSelectionEvent e) {
    Object o = mainFrame.getContactListPanel().getContactList().getSelectedValue();

    if ((e.getFirstIndex() != -1 || e.getLastIndex() != -1) && (o instanceof MetaContact)) {
      setCallMetaContact(true);

      // Switch automatically to the appropriate pps in account selector
      // box and enable callButton if telephony is supported.
      Contact contact = ((MetaContact) o).getDefaultContact(OperationSetBasicTelephony.class);

      if (contact != null) {
        callButton.setEnabled(true);

        if (contact.getProtocolProvider().isRegistered())
          getAccountSelectorBox().setSelected(contact.getProtocolProvider());
      } else {
        callButton.setEnabled(false);
      }
    } else if (phoneNumberCombo.isComboFieldEmpty()) {
      callButton.setEnabled(false);
    }
  }

  /**
   * Implements ChangeListener.stateChanged. Enables the hangup button if ones selects a tab in the
   * main tabbed pane that contains a call panel.
   */
  public void stateChanged(ChangeEvent e) {
    this.updateButtonsStateAccordingToSelectedPanel();

    Component selectedPanel = mainFrame.getSelectedTab();
    if (selectedPanel == null || !(selectedPanel instanceof CallPanel)) {
      Iterator callPanels = activeCalls.values().iterator();

      while (callPanels.hasNext()) {
        CallPanel callPanel = (CallPanel) callPanels.next();

        callPanel.removeDialogs();
      }
    }
  }

  /** Updates call and hangup buttons' states aa */
  private void updateButtonsStateAccordingToSelectedPanel() {
    Component selectedPanel = mainFrame.getSelectedTab();
    if (selectedPanel != null && selectedPanel instanceof CallPanel) {
      this.hangupButton.setEnabled(true);
    } else {
      this.hangupButton.setEnabled(false);
    }
  }

  /**
   * Returns the call button.
   *
   * @return the call button
   */
  public SIPCommButton getCallButton() {
    return callButton;
  }

  /**
   * Returns the hangup button.
   *
   * @return the hangup button
   */
  public SIPCommButton getHangupButton() {
    return hangupButton;
  }

  /**
   * Returns the main application frame. Meant to be used from the contained components that do not
   * have direct access to the MainFrame.
   *
   * @return the main application frame
   */
  public MainFrame getMainFrame() {
    return mainFrame;
  }

  /**
   * Returns the combo box, where user enters the phone number to call to.
   *
   * @return the combo box, where user enters the phone number to call to.
   */
  public JComboBox getCallComboBox() {
    return phoneNumberCombo;
  }

  /**
   * Answers the given call.
   *
   * @param call the call to answer
   */
  public void answerCall(Call call) {
    new AnswerCallThread(call).start();
  }

  /**
   * Returns TRUE if this call is a call to an internal meta contact from the contact list,
   * otherwise returns FALSE.
   *
   * @return TRUE if this call is a call to an internal meta contact from the contact list,
   *     otherwise returns FALSE
   */
  public boolean isCallMetaContact() {
    return isCallMetaContact;
  }

  /**
   * Sets the isCallMetaContact variable to TRUE or FALSE. This defines if this call is a call to a
   * given meta contact selected from the contact list or a call to an external contact or phone
   * number.
   *
   * @param isCallMetaContact TRUE to define this call as a call to an internal meta contact and
   *     FALSE to define it as a call to an external contact or phone number.
   */
  public void setCallMetaContact(boolean isCallMetaContact) {
    this.isCallMetaContact = isCallMetaContact;
  }

  /**
   * Creates a call to the contact represented by the given string.
   *
   * @param contact the contact to call to
   */
  public void createCall(String contact) {
    CallPanel callPanel = new CallPanel(this, contact);

    mainFrame.addCallPanel(callPanel);

    new CreateCallThread(contact, callPanel).start();
  }

  /**
   * Creates a call to the given list of contacts.
   *
   * @param contacts the list of contacts to call to
   */
  public void createCall(Vector contacts) {
    CallPanel callPanel = new CallPanel(this, contacts);

    mainFrame.addCallPanel(callPanel);

    new CreateCallThread(contacts, callPanel).start();
  }

  /** Creates a call from a given Contact or a given String. */
  private class CreateCallThread extends Thread {
    Vector contacts;

    CallPanel callPanel;

    String stringContact;

    OperationSetBasicTelephony telephony;

    public CreateCallThread(String contact, CallPanel callPanel) {
      this.stringContact = contact;
      this.callPanel = callPanel;

      if (selectedCallProvider != null)
        telephony = mainFrame.getTelephonyOpSet(selectedCallProvider);
    }

    public CreateCallThread(Vector contacts, CallPanel callPanel) {
      this.contacts = contacts;
      this.callPanel = callPanel;

      if (selectedCallProvider != null)
        telephony = mainFrame.getTelephonyOpSet(selectedCallProvider);
    }

    public void run() {
      if (telephony == null) return;

      Call createdCall = null;

      if (contacts != null) {
        Contact contact = (Contact) contacts.get(0);

        // NOTE: The multi user call is not yet implemented!
        // We just get the first contact and create a call for him.
        try {
          createdCall = telephony.createCall(contact);
        } catch (OperationFailedException e) {
          logger.error("The call could not be created: " + e);

          callPanel.getParticipantPanel(contact.getDisplayName()).setState(e.getMessage());

          removeCallPanelWait(callPanel);
        }

        // If the call is successfully created we set the created
        // Call instance to the already existing CallPanel and we
        // add this call to the active calls.
        if (createdCall != null) {
          callPanel.setCall(createdCall, GuiCallParticipantRecord.OUTGOING_CALL);

          activeCalls.put(createdCall, callPanel);
        }
      } else {
        try {
          createdCall = telephony.createCall(stringContact);
        } catch (ParseException e) {
          logger.error("The call could not be created: " + e);

          callPanel.getParticipantPanel(stringContact).setState(e.getMessage());

          removeCallPanelWait(callPanel);
        } catch (OperationFailedException e) {
          logger.error("The call could not be created: " + e);

          callPanel.getParticipantPanel(stringContact).setState(e.getMessage());

          removeCallPanelWait(callPanel);
        }

        // If the call is successfully created we set the created
        // Call instance to the already existing CallPanel and we
        // add this call to the active calls.
        if (createdCall != null) {
          callPanel.setCall(createdCall, GuiCallParticipantRecord.OUTGOING_CALL);

          activeCalls.put(createdCall, callPanel);
        }
      }
    }
  }

  /** Answers all call participants in the given call. */
  private class AnswerCallThread extends Thread {
    private Call call;

    public AnswerCallThread(Call call) {
      this.call = call;
    }

    public void run() {
      ProtocolProviderService pps = call.getProtocolProvider();

      Iterator participants = call.getCallParticipants();

      while (participants.hasNext()) {
        CallParticipant participant = (CallParticipant) participants.next();

        OperationSetBasicTelephony telephony = mainFrame.getTelephonyOpSet(pps);

        try {
          telephony.answerCallParticipant(participant);
        } catch (OperationFailedException e) {
          logger.error(
              "Could not answer to : " + participant + " caused by the following exception: " + e);
        }
      }
    }
  }
}
Exemple #7
0
  /**
   * Handles the <tt>ActionEvent</tt> generated when user presses one of the buttons in this panel.
   */
  public void actionPerformed(ActionEvent evt) {
    JButton button = (JButton) evt.getSource();
    String buttonName = button.getName();

    if (buttonName.equals("call")) {
      Component selectedPanel = mainFrame.getSelectedTab();

      // call button is pressed over an already open call panel
      if (selectedPanel != null
          && selectedPanel instanceof CallPanel
          && ((CallPanel) selectedPanel).getCall().getCallState()
              == CallState.CALL_INITIALIZATION) {

        NotificationManager.stopSound(NotificationManager.BUSY_CALL);
        NotificationManager.stopSound(NotificationManager.INCOMING_CALL);

        CallPanel callPanel = (CallPanel) selectedPanel;

        Iterator participantPanels = callPanel.getParticipantsPanels();

        while (participantPanels.hasNext()) {
          CallParticipantPanel panel = (CallParticipantPanel) participantPanels.next();

          panel.setState("Connecting");
        }

        Call call = callPanel.getCall();

        answerCall(call);
      }
      // call button is pressed over the call list
      else if (selectedPanel != null
          && selectedPanel instanceof CallListPanel
          && ((CallListPanel) selectedPanel).getCallList().getSelectedIndex() != -1) {

        CallListPanel callListPanel = (CallListPanel) selectedPanel;

        GuiCallParticipantRecord callRecord =
            (GuiCallParticipantRecord) callListPanel.getCallList().getSelectedValue();

        String stringContact = callRecord.getParticipantName();

        createCall(stringContact);
      }
      // call button is pressed over the contact list
      else if (selectedPanel != null && selectedPanel instanceof ContactListPanel) {
        // call button is pressed when a meta contact is selected
        if (isCallMetaContact) {
          Object[] selectedContacts =
              mainFrame.getContactListPanel().getContactList().getSelectedValues();

          Vector telephonyContacts = new Vector();

          for (int i = 0; i < selectedContacts.length; i++) {

            Object o = selectedContacts[i];

            if (o instanceof MetaContact) {

              Contact contact =
                  ((MetaContact) o).getDefaultContact(OperationSetBasicTelephony.class);

              if (contact != null) telephonyContacts.add(contact);
              else {
                new ErrorDialog(
                        this.mainFrame,
                        Messages.getI18NString("warning").getText(),
                        Messages.getI18NString(
                                "contactNotSupportingTelephony",
                                new String[] {((MetaContact) o).getDisplayName()})
                            .getText())
                    .showDialog();
              }
            }
          }

          if (telephonyContacts.size() > 0) createCall(telephonyContacts);

        } else if (!phoneNumberCombo.isComboFieldEmpty()) {

          // if no contact is selected checks if the user has chosen
          // or has
          // writen something in the phone combo box

          String stringContact = phoneNumberCombo.getEditor().getItem().toString();

          createCall(stringContact);
        }
      } else if (selectedPanel != null && selectedPanel instanceof DialPanel) {
        String stringContact = phoneNumberCombo.getEditor().getItem().toString();
        createCall(stringContact);
      }
    } else if (buttonName.equalsIgnoreCase("hangup")) {
      Component selectedPanel = this.mainFrame.getSelectedTab();

      if (selectedPanel != null && selectedPanel instanceof CallPanel) {

        NotificationManager.stopSound(NotificationManager.BUSY_CALL);
        NotificationManager.stopSound(NotificationManager.INCOMING_CALL);
        NotificationManager.stopSound(NotificationManager.OUTGOING_CALL);

        CallPanel callPanel = (CallPanel) selectedPanel;

        Call call = callPanel.getCall();

        if (removeCallTimers.containsKey(callPanel)) {
          ((Timer) removeCallTimers.get(callPanel)).stop();
          removeCallTimers.remove(callPanel);
        }

        removeCallPanel(callPanel);

        if (call != null) {
          ProtocolProviderService pps = call.getProtocolProvider();

          OperationSetBasicTelephony telephony = mainFrame.getTelephonyOpSet(pps);

          Iterator participants = call.getCallParticipants();

          while (participants.hasNext()) {
            try {
              // now we hang up the first call participant in the
              // call
              telephony.hangupCallParticipant((CallParticipant) participants.next());
            } catch (OperationFailedException e) {
              logger.error("Hang up was not successful: " + e);
            }
          }
        }
      }
    } else if (buttonName.equalsIgnoreCase("minimize")) {
      JCheckBoxMenuItem hideCallPanelItem =
          mainFrame.getMainMenu().getViewMenu().getHideCallPanelItem();

      if (!hideCallPanelItem.isSelected()) hideCallPanelItem.setSelected(true);

      this.setCallPanelVisible(false);
    } else if (buttonName.equalsIgnoreCase("restore")) {

      JCheckBoxMenuItem hideCallPanelItem =
          mainFrame.getMainMenu().getViewMenu().getHideCallPanelItem();

      if (hideCallPanelItem.isSelected()) hideCallPanelItem.setSelected(false);

      this.setCallPanelVisible(true);
    }
  }
  /**
   * Handles buttons action events.
   *
   * @param evt the <tt>ActionEvent</tt> that notified us
   */
  public void actionPerformed(ActionEvent evt) {
    JButton sourceButton = (JButton) evt.getSource();

    if (sourceButton.equals(openFileButton)) {
      this.openFile(downloadFile);
    } else if (sourceButton.equals(openFolderButton)) {
      try {
        File downloadDir = GuiActivator.getFileAccessService().getDefaultDownloadDirectory();

        GuiActivator.getDesktopService().open(downloadDir);
      } catch (IllegalArgumentException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_DOES_NOT_EXIST"));
      } catch (NullPointerException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_DOES_NOT_EXIST"));
      } catch (UnsupportedOperationException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FILE_OPEN_NOT_SUPPORTED"));
      } catch (SecurityException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_OPEN_NO_PERMISSION"));
      } catch (IOException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_OPEN_NO_APPLICATION"));
      } catch (Exception e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open file.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_OPEN_FAILED"));
      }
    } else if (sourceButton.equals(cancelButton)) {
      if (fileTransfer != null) fileTransfer.cancel();
    }
  }
/**
 * The <tt>FileTransferConversationComponent</tt> is the parent of all file conversation components
 * - for incoming, outgoing and history file transfers.
 *
 * @author Yana Stamcheva
 * @author Adam Netocny
 */
public abstract class FileTransferConversationComponent extends ChatConversationComponent
    implements ActionListener, FileTransferProgressListener, Skinnable {
  /** The logger for this class. */
  private final Logger logger = Logger.getLogger(FileTransferConversationComponent.class);

  /** Image default width. */
  protected static final int IMAGE_WIDTH = 64;

  /** Image default height. */
  protected static final int IMAGE_HEIGHT = 64;

  /** The image label. */
  protected final FileImageLabel imageLabel;

  /** The title label. */
  protected final JLabel titleLabel = new JLabel();

  /** The file label. */
  protected final JLabel fileLabel = new JLabel();

  /** The error area. */
  private final JTextArea errorArea = new JTextArea();

  /** The error icon label. */
  private final JLabel errorIconLabel =
      new JLabel(new ImageIcon(ImageLoader.getImage(ImageLoader.EXCLAMATION_MARK)));

  /** The cancel button. */
  protected final ChatConversationButton cancelButton = new ChatConversationButton();

  /** The retry button. */
  protected final ChatConversationButton retryButton = new ChatConversationButton();

  /** The accept button. */
  protected final ChatConversationButton acceptButton = new ChatConversationButton();

  /** The reject button. */
  protected final ChatConversationButton rejectButton = new ChatConversationButton();

  /** The open file button. */
  protected final ChatConversationButton openFileButton = new ChatConversationButton();

  /** The open folder button. */
  protected final ChatConversationButton openFolderButton = new ChatConversationButton();

  /** The progress bar. */
  protected final JProgressBar progressBar = new JProgressBar();

  /** The progress properties panel. */
  private final TransparentPanel progressPropertiesPanel =
      new TransparentPanel(new FlowLayout(FlowLayout.RIGHT));

  /** The progress speed label. */
  private final JLabel progressSpeedLabel = new JLabel();

  /** The estimated time label. */
  private final JLabel estimatedTimeLabel = new JLabel();

  /** The download file. */
  private File downloadFile;

  /** The file transfer. */
  private FileTransfer fileTransfer;

  /** The speed calculated delay. */
  private static final int SPEED_CALCULATE_DELAY = 5000;

  /** The transferred file size. */
  private long transferredFileSize = 0;

  /** The time of the last calculated transfer speed. */
  private long lastSpeedTimestamp = 0;

  /** The last estimated time for the transfer. */
  private long lastEstimatedTimeTimestamp = 0;

  /** The number of bytes last transferred. */
  private long lastTransferredBytes = 0;

  /** The last calculated progress speed. */
  private long lastProgressSpeed;

  /** The last estimated time. */
  private long lastEstimatedTime;

  /** Creates a file conversation component. */
  public FileTransferConversationComponent() {
    imageLabel = new FileImageLabel();

    constraints.gridx = 0;
    constraints.gridy = 0;
    constraints.gridwidth = 1;
    constraints.gridheight = 4;
    constraints.anchor = GridBagConstraints.NORTHWEST;
    constraints.insets = new Insets(5, 5, 5, 5);

    add(imageLabel, constraints);
    imageLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_FILE_ICON)));

    constraints.gridx = 1;
    constraints.gridy = 0;
    constraints.gridwidth = 3;
    constraints.gridheight = 1;
    constraints.fill = GridBagConstraints.HORIZONTAL;
    constraints.weightx = 1.0;
    constraints.anchor = GridBagConstraints.NORTHWEST;
    constraints.insets = new Insets(5, 5, 5, 5);

    add(titleLabel, constraints);
    titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 11f));

    constraints.gridx = 1;
    constraints.gridy = 1;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 5, 5);

    add(fileLabel, constraints);

    constraints.gridx = 1;
    constraints.gridy = 2;
    constraints.gridwidth = 1;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.NONE;

    add(errorIconLabel, constraints);
    errorIconLabel.setVisible(false);

    constraints.gridx = 2;
    constraints.gridy = 2;
    constraints.gridwidth = 2;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.HORIZONTAL;

    add(errorArea, constraints);
    errorArea.setForeground(new Color(resources.getColor("service.gui.ERROR_FOREGROUND")));
    setTextAreaStyle(errorArea);
    errorArea.setVisible(false);

    constraints.gridx = 1;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);

    add(retryButton, constraints);
    retryButton.setText(GuiActivator.getResources().getI18NString("service.gui.RETRY"));
    retryButton.setVisible(false);

    constraints.gridx = 1;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);

    add(cancelButton, constraints);
    cancelButton.setText(GuiActivator.getResources().getI18NString("service.gui.CANCEL"));
    cancelButton.addActionListener(this);
    cancelButton.setVisible(false);

    constraints.gridx = 2;
    constraints.gridy = 3;
    constraints.gridwidth = GridBagConstraints.RELATIVE;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.fill = GridBagConstraints.NONE;
    constraints.anchor = GridBagConstraints.EAST;
    constraints.insets = new Insets(0, 5, 0, 5);

    constraints.gridx = 3;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.fill = GridBagConstraints.NONE;
    constraints.anchor = GridBagConstraints.LINE_END;
    constraints.insets = new Insets(0, 5, 0, 5);

    add(progressPropertiesPanel, constraints);

    estimatedTimeLabel.setFont(estimatedTimeLabel.getFont().deriveFont(11f));
    estimatedTimeLabel.setVisible(false);
    progressSpeedLabel.setFont(progressSpeedLabel.getFont().deriveFont(11f));
    progressSpeedLabel.setVisible(false);

    progressPropertiesPanel.add(progressSpeedLabel);
    progressPropertiesPanel.add(estimatedTimeLabel);

    constraints.gridx = 1;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.NONE;

    add(acceptButton, constraints);
    acceptButton.setText(GuiActivator.getResources().getI18NString("service.gui.ACCEPT"));
    acceptButton.setVisible(false);

    constraints.gridx = 2;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.NONE;

    add(rejectButton, constraints);
    rejectButton.setText(GuiActivator.getResources().getI18NString("service.gui.REJECT"));
    rejectButton.setVisible(false);

    constraints.gridx = 1;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.NONE;

    add(openFileButton, constraints);
    openFileButton.setText(GuiActivator.getResources().getI18NString("service.gui.OPEN"));
    openFileButton.setVisible(false);
    openFileButton.addActionListener(this);

    constraints.gridx = 2;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.NONE;

    add(openFolderButton, constraints);
    openFolderButton.setText(GuiActivator.getResources().getI18NString("service.gui.OPEN_FOLDER"));
    openFolderButton.setVisible(false);
    openFolderButton.addActionListener(this);

    constraints.gridx = 1;
    constraints.gridy = 2;
    constraints.gridwidth = 3;
    constraints.gridheight = 1;
    constraints.weightx = 1.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.ipadx = 150;
    constraints.fill = GridBagConstraints.HORIZONTAL;

    add(progressBar, constraints);
    progressBar.setVisible(false);
    progressBar.setStringPainted(true);
  }

  /**
   * Sets a custom style for the given text area.
   *
   * @param textArea the text area to style
   */
  private void setTextAreaStyle(JTextArea textArea) {
    textArea.setOpaque(false);
    textArea.setLineWrap(true);
    textArea.setWrapStyleWord(true);
  }

  /**
   * Shows the given error message in the error area of this component.
   *
   * @param message the message to show
   */
  protected void showErrorMessage(String message) {
    errorArea.setText(message);
    errorIconLabel.setVisible(true);
    errorArea.setVisible(true);
  }

  /**
   * Sets the download file.
   *
   * @param file the file that has been downloaded or sent
   */
  protected void setCompletedDownloadFile(File file) {
    this.downloadFile = file;

    imageLabel.setFile(downloadFile);

    imageLabel.setToolTipText(resources.getI18NString("service.gui.OPEN_FILE_FROM_IMAGE"));

    imageLabel.addMouseListener(
        new MouseAdapter() {
          public void mouseClicked(MouseEvent e) {
            if (e.getClickCount() > 1) {
              openFile(downloadFile);
            }
          }
        });
  }

  /**
   * Sets the file transfer.
   *
   * @param fileTransfer the file transfer
   * @param transferredFileSize the size of the transferred file
   */
  protected void setFileTransfer(FileTransfer fileTransfer, long transferredFileSize) {
    this.fileTransfer = fileTransfer;
    this.transferredFileSize = transferredFileSize;

    fileTransfer.addProgressListener(this);
  }

  /**
   * Handles buttons action events.
   *
   * @param evt the <tt>ActionEvent</tt> that notified us
   */
  public void actionPerformed(ActionEvent evt) {
    JButton sourceButton = (JButton) evt.getSource();

    if (sourceButton.equals(openFileButton)) {
      this.openFile(downloadFile);
    } else if (sourceButton.equals(openFolderButton)) {
      try {
        File downloadDir = GuiActivator.getFileAccessService().getDefaultDownloadDirectory();

        GuiActivator.getDesktopService().open(downloadDir);
      } catch (IllegalArgumentException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_DOES_NOT_EXIST"));
      } catch (NullPointerException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_DOES_NOT_EXIST"));
      } catch (UnsupportedOperationException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FILE_OPEN_NOT_SUPPORTED"));
      } catch (SecurityException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_OPEN_NO_PERMISSION"));
      } catch (IOException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_OPEN_NO_APPLICATION"));
      } catch (Exception e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open file.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_OPEN_FAILED"));
      }
    } else if (sourceButton.equals(cancelButton)) {
      if (fileTransfer != null) fileTransfer.cancel();
    }
  }

  /**
   * Updates progress bar progress line every time a progress event has been received.
   *
   * @param event the <tt>FileTransferProgressEvent</tt> that notified us
   */
  public void progressChanged(FileTransferProgressEvent event) {
    progressBar.setValue((int) event.getProgress());

    long transferredBytes = event.getFileTransfer().getTransferedBytes();
    long progressTimestamp = event.getTimestamp();

    ByteFormat format = new ByteFormat();
    String bytesString = format.format(transferredBytes);

    if ((progressTimestamp - lastSpeedTimestamp) >= SPEED_CALCULATE_DELAY) {
      lastProgressSpeed = Math.round(calculateProgressSpeed(transferredBytes));

      this.lastSpeedTimestamp = progressTimestamp;
      this.lastTransferredBytes = transferredBytes;
    }

    if ((progressTimestamp - lastEstimatedTimeTimestamp) >= SPEED_CALCULATE_DELAY
        && lastProgressSpeed > 0) {
      lastEstimatedTime =
          Math.round(
              calculateEstimatedTransferTime(
                  lastProgressSpeed, transferredFileSize - transferredBytes));

      lastEstimatedTimeTimestamp = progressTimestamp;
    }

    progressBar.setString(getProgressLabel(bytesString));

    if (lastProgressSpeed > 0) {
      progressSpeedLabel.setText(
          resources.getI18NString("service.gui.SPEED") + format.format(lastProgressSpeed) + "/sec");
      progressSpeedLabel.setVisible(true);
    }

    if (lastEstimatedTime > 0) {
      estimatedTimeLabel.setText(
          resources.getI18NString("service.gui.ESTIMATED_TIME")
              + GuiUtils.formatSeconds(lastEstimatedTime * 1000));
      estimatedTimeLabel.setVisible(true);
    }
  }

  /**
   * Returns the string, showing information for the given file.
   *
   * @param file the file
   * @return the name of the given file
   */
  protected String getFileLabel(File file) {
    String fileName = file.getName();
    long fileSize = file.length();

    ByteFormat format = new ByteFormat();
    String text = format.format(fileSize);

    return fileName + " (" + text + ")";
  }

  /**
   * Returns the string, showing information for the given file.
   *
   * @param fileName the name of the file
   * @param fileSize the size of the file
   * @return the name of the given file
   */
  protected String getFileLabel(String fileName, long fileSize) {
    ByteFormat format = new ByteFormat();
    String text = format.format(fileSize);

    return fileName + " (" + text + ")";
  }

  /** Hides all progress related components. */
  protected void hideProgressRelatedComponents() {
    progressBar.setVisible(false);
    progressSpeedLabel.setVisible(false);
    estimatedTimeLabel.setVisible(false);
  }

  /**
   * Returns the label to show on the progress bar.
   *
   * @param bytesString the bytes that have been transfered
   * @return the label to show on the progress bar
   */
  protected abstract String getProgressLabel(String bytesString);

  /**
   * Returns the speed of the transfer.
   *
   * @param transferredBytes the number of bytes that have been transferred
   * @return the speed of the transfer
   */
  private double calculateProgressSpeed(long transferredBytes) {
    // Bytes per second = bytes / SPEED_CALCULATE_DELAY miliseconds * 1000.
    return (transferredBytes - lastTransferredBytes) / SPEED_CALCULATE_DELAY * 1000;
  }

  /**
   * Returns the estimated transfer time left.
   *
   * @param speed the speed of the transfer
   * @param bytesLeft the size of the file
   * @return the estimated transfer time left
   */
  private double calculateEstimatedTransferTime(double speed, long bytesLeft) {
    return bytesLeft / speed;
  }

  /** Reload images and colors. */
  public void loadSkin() {
    errorIconLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.EXCLAMATION_MARK)));

    if (downloadFile != null)
      imageLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_FILE_ICON)));

    errorArea.setForeground(new Color(resources.getColor("service.gui.ERROR_FOREGROUND")));
  }
}
/**
 * The single chat implementation of the <tt>ChatTransport</tt> interface that provides abstraction
 * to protocol provider access.
 *
 * @author Yana Stamcheva
 */
public class MetaContactChatTransport implements ChatTransport, ContactPresenceStatusListener {
  /** The logger. */
  private static final Logger logger = Logger.getLogger(MetaContactChatTransport.class);

  /** The parent <tt>ChatSession</tt>, where this transport is available. */
  private final MetaContactChatSession parentChatSession;

  /** The associated protocol <tt>Contact</tt>. */
  private final Contact contact;

  /** The resource associated with this contact. */
  private ContactResource contactResource;

  /** The protocol presence operation set associated with this transport. */
  private final OperationSetPresence presenceOpSet;

  /** The thumbnail default width. */
  private static final int THUMBNAIL_WIDTH = 64;

  /** The thumbnail default height. */
  private static final int THUMBNAIL_HEIGHT = 64;

  /** Indicates if only the resource name should be displayed. */
  private boolean isDisplayResourceOnly = false;

  /**
   * Creates an instance of <tt>MetaContactChatTransport</tt> by specifying the parent
   * <tt>chatSession</tt> and the <tt>contact</tt> associated with the transport.
   *
   * @param chatSession the parent <tt>ChatSession</tt>
   * @param contact the <tt>Contact</tt> associated with this transport
   */
  public MetaContactChatTransport(MetaContactChatSession chatSession, Contact contact) {
    this(chatSession, contact, null, false);
  }

  /**
   * Creates an instance of <tt>MetaContactChatTransport</tt> by specifying the parent
   * <tt>chatSession</tt> and the <tt>contact</tt> associated with the transport.
   *
   * @param chatSession the parent <tt>ChatSession</tt>
   * @param contact the <tt>Contact</tt> associated with this transport
   * @param contactResource the <tt>ContactResource</tt> associated with the contact
   * @param isDisplayResourceOnly indicates if only the resource name should be displayed
   */
  public MetaContactChatTransport(
      MetaContactChatSession chatSession,
      Contact contact,
      ContactResource contactResource,
      boolean isDisplayResourceOnly) {
    this.parentChatSession = chatSession;
    this.contact = contact;
    this.contactResource = contactResource;
    this.isDisplayResourceOnly = isDisplayResourceOnly;

    presenceOpSet = contact.getProtocolProvider().getOperationSet(OperationSetPresence.class);

    if (presenceOpSet != null) presenceOpSet.addContactPresenceStatusListener(this);

    // checking this can be slow so make
    // sure its out of our way
    new Thread(
            new Runnable() {
              public void run() {
                checkImCaps();
              }
            })
        .start();
  }

  /**
   * If sending im is supported check it for supporting html messages if a font is set. As it can be
   * slow make sure its not on our way
   */
  private void checkImCaps() {
    if (ConfigurationUtils.getChatDefaultFontFamily() != null
        && ConfigurationUtils.getChatDefaultFontSize() > 0) {
      OperationSetBasicInstantMessaging imOpSet =
          contact.getProtocolProvider().getOperationSet(OperationSetBasicInstantMessaging.class);

      if (imOpSet != null)
        imOpSet.isContentTypeSupported(OperationSetBasicInstantMessaging.HTML_MIME_TYPE, contact);
    }
  }

  /**
   * Returns the contact associated with this transport.
   *
   * @return the contact associated with this transport
   */
  public Contact getContact() {
    return contact;
  }

  /**
   * Returns the contact address corresponding to this chat transport.
   *
   * @return The contact address corresponding to this chat transport.
   */
  public String getName() {
    return contact.getAddress();
  }

  /**
   * Returns the display name corresponding to this chat transport.
   *
   * @return The display name corresponding to this chat transport.
   */
  public String getDisplayName() {
    return contact.getDisplayName();
  }

  /**
   * Returns the resource name of this chat transport. This is for example the name of the user
   * agent from which the contact is logged.
   *
   * @return The display name of this chat transport resource.
   */
  public String getResourceName() {
    if (contactResource != null) return contactResource.getResourceName();

    return null;
  }

  public boolean isDisplayResourceOnly() {
    return isDisplayResourceOnly;
  }

  /**
   * Returns the presence status of this transport.
   *
   * @return the presence status of this transport.
   */
  public PresenceStatus getStatus() {
    if (contactResource != null) return contactResource.getPresenceStatus();
    else return contact.getPresenceStatus();
  }

  /**
   * Returns the <tt>ProtocolProviderService</tt>, corresponding to this chat transport.
   *
   * @return the <tt>ProtocolProviderService</tt>, corresponding to this chat transport.
   */
  public ProtocolProviderService getProtocolProvider() {
    return contact.getProtocolProvider();
  }

  /**
   * Returns <code>true</code> if this chat transport supports instant messaging, otherwise returns
   * <code>false</code>.
   *
   * @return <code>true</code> if this chat transport supports instant messaging, otherwise returns
   *     <code>false</code>.
   */
  public boolean allowsInstantMessage() {
    // First try to ask the capabilities operation set if such is
    // available.
    OperationSetContactCapabilities capOpSet =
        getProtocolProvider().getOperationSet(OperationSetContactCapabilities.class);

    if (capOpSet != null) {
      if (capOpSet.getOperationSet(contact, OperationSetBasicInstantMessaging.class) != null) {
        return true;
      }
    } else if (contact
            .getProtocolProvider()
            .getOperationSet(OperationSetBasicInstantMessaging.class)
        != null) return true;

    return false;
  }

  /**
   * Returns <code>true</code> if this chat transport supports message corrections and false
   * otherwise.
   *
   * @return <code>true</code> if this chat transport supports message corrections and false
   *     otherwise.
   */
  public boolean allowsMessageCorrections() {
    OperationSetContactCapabilities capOpSet =
        getProtocolProvider().getOperationSet(OperationSetContactCapabilities.class);

    if (capOpSet != null) {
      return capOpSet.getOperationSet(contact, OperationSetMessageCorrection.class) != null;
    } else {
      return contact.getProtocolProvider().getOperationSet(OperationSetMessageCorrection.class)
          != null;
    }
  }

  /**
   * Returns <code>true</code> if this chat transport supports sms messaging, otherwise returns
   * <code>false</code>.
   *
   * @return <code>true</code> if this chat transport supports sms messaging, otherwise returns
   *     <code>false</code>.
   */
  public boolean allowsSmsMessage() {
    // First try to ask the capabilities operation set if such is
    // available.
    OperationSetContactCapabilities capOpSet =
        getProtocolProvider().getOperationSet(OperationSetContactCapabilities.class);

    if (capOpSet != null) {
      if (capOpSet.getOperationSet(contact, OperationSetSmsMessaging.class) != null) {
        return true;
      }
    } else if (contact.getProtocolProvider().getOperationSet(OperationSetSmsMessaging.class)
        != null) return true;

    return false;
  }

  /**
   * Returns <code>true</code> if this chat transport supports typing notifications, otherwise
   * returns <code>false</code>.
   *
   * @return <code>true</code> if this chat transport supports typing notifications, otherwise
   *     returns <code>false</code>.
   */
  public boolean allowsTypingNotifications() {
    Object tnOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetTypingNotifications.class);

    if (tnOpSet != null) return true;
    else return false;
  }

  /**
   * Returns <code>true</code> if this chat transport supports file transfer, otherwise returns
   * <code>false</code>.
   *
   * @return <code>true</code> if this chat transport supports file transfer, otherwise returns
   *     <code>false</code>.
   */
  public boolean allowsFileTransfer() {
    Object ftOpSet = contact.getProtocolProvider().getOperationSet(OperationSetFileTransfer.class);

    if (ftOpSet != null) return true;
    else return false;
  }

  /**
   * Sends the given instant message through this chat transport, by specifying the mime type (html
   * or plain text).
   *
   * @param message The message to send.
   * @param mimeType The mime type of the message to send: text/html or text/plain.
   * @throws Exception if the send operation is interrupted
   */
  public void sendInstantMessage(String message, String mimeType) throws Exception {
    // If this chat transport does not support instant messaging we do
    // nothing here.
    if (!allowsInstantMessage()) return;

    OperationSetBasicInstantMessaging imOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetBasicInstantMessaging.class);

    Message msg;
    if (mimeType.equals(OperationSetBasicInstantMessaging.HTML_MIME_TYPE)
        && imOpSet.isContentTypeSupported(OperationSetBasicInstantMessaging.HTML_MIME_TYPE)) {
      msg =
          imOpSet.createMessage(
              message, OperationSetBasicInstantMessaging.HTML_MIME_TYPE, "utf-8", "");
    } else {
      msg = imOpSet.createMessage(message);
    }

    if (contactResource != null) imOpSet.sendInstantMessage(contact, contactResource, msg);
    else imOpSet.sendInstantMessage(contact, ContactResource.BASE_RESOURCE, msg);
  }

  /**
   * Sends <tt>message</tt> as a message correction through this transport, specifying the mime type
   * (html or plain text) and the id of the message to replace.
   *
   * @param message The message to send.
   * @param mimeType The mime type of the message to send: text/html or text/plain.
   * @param correctedMessageUID The ID of the message being corrected by this message.
   */
  public void correctInstantMessage(String message, String mimeType, String correctedMessageUID) {
    if (!allowsMessageCorrections()) {
      return;
    }

    OperationSetMessageCorrection mcOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetMessageCorrection.class);

    Message msg;
    if (mimeType.equals(OperationSetBasicInstantMessaging.HTML_MIME_TYPE)
        && mcOpSet.isContentTypeSupported(OperationSetBasicInstantMessaging.HTML_MIME_TYPE)) {
      msg =
          mcOpSet.createMessage(
              message, OperationSetBasicInstantMessaging.HTML_MIME_TYPE, "utf-8", "");
    } else {
      msg = mcOpSet.createMessage(message);
    }

    mcOpSet.correctMessage(contact, contactResource, msg, correctedMessageUID);
  }

  /**
   * Determines whether this chat transport supports the supplied content type
   *
   * @param contentType the type we want to check
   * @return <tt>true</tt> if the chat transport supports it and <tt>false</tt> otherwise.
   */
  public boolean isContentTypeSupported(String contentType) {
    OperationSetBasicInstantMessaging imOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetBasicInstantMessaging.class);

    if (imOpSet != null) return imOpSet.isContentTypeSupported(contentType);
    else return false;
  }

  /**
   * Sends the given sms message trough this chat transport.
   *
   * @param phoneNumber phone number of the destination
   * @param messageText The message to send.
   * @throws Exception if the send operation is interrupted
   */
  public void sendSmsMessage(String phoneNumber, String messageText) throws Exception {
    // If this chat transport does not support sms messaging we do
    // nothing here.
    if (!allowsSmsMessage()) return;

    SMSManager.sendSMS(contact.getProtocolProvider(), phoneNumber, messageText);
  }

  /**
   * Whether a dialog need to be opened so the user can enter the destination number.
   *
   * @return <tt>true</tt> if dialog needs to be open.
   */
  public boolean askForSMSNumber() {
    // If this chat transport does not support sms messaging we do
    // nothing here.
    if (!allowsSmsMessage()) return false;

    OperationSetSmsMessaging smsOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetSmsMessaging.class);

    return smsOpSet.askForNumber(contact);
  }

  /**
   * Sends the given sms message trough this chat transport.
   *
   * @param message the message to send
   * @throws Exception if the send operation is interrupted
   */
  public void sendSmsMessage(String message) throws Exception {
    // If this chat transport does not support sms messaging we do
    // nothing here.
    if (!allowsSmsMessage()) return;

    SMSManager.sendSMS(contact, message);
  }

  /**
   * Sends a typing notification state.
   *
   * @param typingState the typing notification state to send
   * @return the result of this operation. One of the TYPING_NOTIFICATION_XXX constants defined in
   *     this class
   */
  public int sendTypingNotification(int typingState) {
    // If this chat transport does not support sms messaging we do
    // nothing here.
    if (!allowsTypingNotifications()) return -1;

    ProtocolProviderService protocolProvider = contact.getProtocolProvider();
    OperationSetTypingNotifications tnOperationSet =
        protocolProvider.getOperationSet(OperationSetTypingNotifications.class);

    // if protocol is not registered or contact is offline don't
    // try to send typing notifications
    if (protocolProvider.isRegistered()
        && contact.getPresenceStatus().getStatus() >= PresenceStatus.ONLINE_THRESHOLD) {
      try {
        tnOperationSet.sendTypingNotification(contact, typingState);

        return ChatPanel.TYPING_NOTIFICATION_SUCCESSFULLY_SENT;
      } catch (Exception ex) {
        logger.error("Failed to send typing notifications.", ex);

        return ChatPanel.TYPING_NOTIFICATION_SEND_FAILED;
      }
    }

    return ChatPanel.TYPING_NOTIFICATION_SEND_FAILED;
  }

  /**
   * Sends the given file through this chat transport file transfer operation set.
   *
   * @param file the file to send
   * @return the <tt>FileTransfer</tt> object charged to transfer the file
   * @throws Exception if anything goes wrong
   */
  public FileTransfer sendFile(File file) throws Exception {
    // If this chat transport does not support instant messaging we do
    // nothing here.
    if (!allowsFileTransfer()) return null;

    OperationSetFileTransfer ftOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetFileTransfer.class);

    if (FileUtils.isImage(file.getName())) {
      // Create a thumbnailed file if possible.
      OperationSetThumbnailedFileFactory tfOpSet =
          contact.getProtocolProvider().getOperationSet(OperationSetThumbnailedFileFactory.class);

      if (tfOpSet != null) {
        byte[] thumbnail = getFileThumbnail(file);

        if (thumbnail != null && thumbnail.length > 0) {
          file =
              tfOpSet.createFileWithThumbnail(
                  file, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, "image/png", thumbnail);
        }
      }
    }
    return ftOpSet.sendFile(contact, file);
  }

  /**
   * Returns the maximum file length supported by the protocol in bytes.
   *
   * @return the file length that is supported.
   */
  public long getMaximumFileLength() {
    OperationSetFileTransfer ftOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetFileTransfer.class);

    return ftOpSet.getMaximumFileLength();
  }

  public void inviteChatContact(String contactAddress, String reason) {}

  /**
   * Returns the parent session of this chat transport. A <tt>ChatSession</tt> could contain more
   * than one transports.
   *
   * @return the parent session of this chat transport
   */
  public ChatSession getParentChatSession() {
    return parentChatSession;
  }

  /**
   * Adds an SMS message listener to this chat transport.
   *
   * @param l The message listener to add.
   */
  public void addSmsMessageListener(MessageListener l) {
    // If this chat transport does not support sms messaging we do
    // nothing here.
    if (!allowsSmsMessage()) return;

    OperationSetSmsMessaging smsOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetSmsMessaging.class);

    smsOpSet.addMessageListener(l);
  }

  /**
   * Adds an instant message listener to this chat transport.
   *
   * @param l The message listener to add.
   */
  public void addInstantMessageListener(MessageListener l) {
    // If this chat transport does not support instant messaging we do
    // nothing here.
    if (!allowsInstantMessage()) return;

    OperationSetBasicInstantMessaging imOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetBasicInstantMessaging.class);

    imOpSet.addMessageListener(l);
  }

  /**
   * Removes the given sms message listener from this chat transport.
   *
   * @param l The message listener to remove.
   */
  public void removeSmsMessageListener(MessageListener l) {
    // If this chat transport does not support sms messaging we do
    // nothing here.
    if (!allowsSmsMessage()) return;

    OperationSetSmsMessaging smsOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetSmsMessaging.class);

    smsOpSet.removeMessageListener(l);
  }

  /**
   * Removes the instant message listener from this chat transport.
   *
   * @param l The message listener to remove.
   */
  public void removeInstantMessageListener(MessageListener l) {
    // If this chat transport does not support instant messaging we do
    // nothing here.
    if (!allowsInstantMessage()) return;

    OperationSetBasicInstantMessaging imOpSet =
        contact.getProtocolProvider().getOperationSet(OperationSetBasicInstantMessaging.class);

    imOpSet.removeMessageListener(l);
  }

  /**
   * Indicates that a contact has changed its status.
   *
   * @param evt The presence event containing information about the contact status change.
   */
  public void contactPresenceStatusChanged(ContactPresenceStatusChangeEvent evt) {
    if (evt.getSourceContact().equals(contact)
        && !evt.getOldStatus().equals(evt.getNewStatus())
        && contactResource == null) // If the contact source is set then the
    // status will be updated from the
    // MetaContactChatSession.
    {
      this.updateContactStatus();
    }
  }

  /** Updates the status of this contact with the new given status. */
  private void updateContactStatus() {
    // Update the status of the given contact in the "send via" selector
    // box.
    parentChatSession.getChatSessionRenderer().updateChatTransportStatus(this);
  }

  /** Removes all previously added listeners. */
  public void dispose() {
    if (presenceOpSet != null) presenceOpSet.removeContactPresenceStatusListener(this);
  }

  /**
   * Returns the descriptor of this chat transport.
   *
   * @return the descriptor of this chat transport
   */
  public Object getDescriptor() {
    return contact;
  }

  /**
   * Sets the icon for the given file.
   *
   * @param file the file to set an icon for
   * @return the byte array containing the thumbnail
   */
  private byte[] getFileThumbnail(File file) {
    byte[] bytes = null;
    if (FileUtils.isImage(file.getName())) {
      try {
        ImageIcon image = new ImageIcon(file.toURI().toURL());
        int width = image.getIconWidth();
        int height = image.getIconHeight();

        if (width > THUMBNAIL_WIDTH) width = THUMBNAIL_WIDTH;
        if (height > THUMBNAIL_HEIGHT) height = THUMBNAIL_HEIGHT;

        bytes = ImageUtils.getScaledInstanceInBytes(image.getImage(), width, height);
      } catch (MalformedURLException e) {
        if (logger.isDebugEnabled()) logger.debug("Could not locate image.", e);
      }
    }
    return bytes;
  }
}