/** * 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; } } } } }
/** * 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); } } } } }
/** * 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; } }