From 02aa358e4f8ca6a776b3dfa5c0ee59718aa829ec Mon Sep 17 00:00:00 2001 From: Ingo Bauersachs Date: Thu, 6 Feb 2014 16:45:22 +0100 Subject: [PATCH] Optional button to open a custom application from the call panel --- resources/images/images.properties | 1 + resources/images/impl/gui/buttons/crm.png | Bin 0 -> 914 bytes resources/languages/resources.properties | 1 + .../impl/gui/main/call/CallPanel.java | 5211 +++++++++-------- .../impl/gui/utils/ImageLoader.java | 6 + 5 files changed, 2644 insertions(+), 2575 deletions(-) create mode 100644 resources/images/impl/gui/buttons/crm.png diff --git a/resources/images/images.properties b/resources/images/images.properties index a862b9830..1b05f4833 100644 --- a/resources/images/images.properties +++ b/resources/images/images.properties @@ -267,6 +267,7 @@ service.gui.buttons.CONTACT_LIST_DIAL_BUTTON=resources/images/impl/gui/buttons/c service.gui.buttons.DIAL_PAD_CALL_BUTTON_BG=resources/images/impl/gui/buttons/dialPadCallButtonBg.png service.gui.buttons.CALL_INFO=resources/images/impl/gui/buttons/callInfo.png service.gui.buttons.ZRTP_ID_BUTTON=resources/images/impl/gui/buttons/zrtpEditId.png +service.gui.buttons.CRM=resources/images/impl/gui/buttons/crm.png service.gui.icons.LEAVE_ICON_BUTTON=resources/images/impl/gui/buttons/leaveRoom.png service.gui.icons.LEAVE_ICON_ROLLOVER_BUTTON=resources/images/impl/gui/buttons/leaveRoomOver.png service.gui.icons.LEAVE_ICON_PRESSED_BUTTON=resources/images/impl/gui/buttons/leaveRoomPressed.png diff --git a/resources/images/impl/gui/buttons/crm.png b/resources/images/impl/gui/buttons/crm.png new file mode 100644 index 0000000000000000000000000000000000000000..490cf6b371609e62e2451199866150f267f728bb GIT binary patch literal 914 zcmV;D18w|?P)N2bPDNB8 zb~7$DE-^7j^FlWO00Rz5L_t(oN9~r&YZFlv#@kL4V|_I-n%1I$`T!-57DAgMF<@xY z6oM@n(lqaaR2PM=R7AT{(3P7C?z`^Bt>8vM@Gqz@5X4oNJ_{}NcczmP!X#RlnQrET zkC{35&OP6px#!%|VZ(+E8~ctfjr)VSxw(N-sdSRt&K-+BYPDK2lgV7<_9%3zMypV* zRwD>~3AZ(_V|!hxRF-FFXRk0tf$9e^c3tK$z7~l@Vwn2iK^Ze0M5%`JO--aO3x&cG zQ(XuwG}`%?k3&O4R~hrEiY@peBE3%j0 zBKQeWUdZS3i|{^M@Ji^YbuCCx)Ro$h8AeAN@z%yPNB=J9yW^O_P223_%Z+{<&1 z)9G~a+_STy$;jn$DLrw`B>NvR-7oJDs~egR1oa_cnX7J+ zG4_*M{ES~NHCC;s2n~e8VK28L)cmdsAp@GoUyb70@CPEL3ror~5r67-yJa6sjWs8- zII4@>&_w(fd8f_|w!v?Nf9ANJR}@7$F5~wsjuvseCy|Y85G;weMa^BMKRCnE#)>5x z(qR9q__w8qtX~l;o!Bl3E+o;6ImuXbz1fLTOr=scad8|P)+5MoIt8VHmUG@nN)48pYnWJBNLo2b204w4 oOV07*qoM6N<$f)KHwN&o-= literal 0 HcmV?d00001 diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index 5944f8b19..bc366b991 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -423,6 +423,7 @@ service.gui.PREFIX=Prefix service.gui.PRESENCE=Presence service.gui.PRESS_ENTER_FOR_SUGGESTIONS='Enter' for suggestions service.gui.PRESS_FOR_CALL_INFO=Press for call info +service.gui.PRESS_TO_OPEN_CRM=Press to open CRM application service.gui.PREVIOUS=Previous service.gui.PREVIOUS_TOOLTIP=Browse older conversations service.gui.PRINT=&Print diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java b/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java index 5a8b99b19..bbe1e9902 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallPanel.java @@ -1,2575 +1,2636 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.impl.gui.main.call; - -import java.awt.*; -import java.awt.Container; -import java.awt.event.*; -import java.beans.*; -import java.util.*; -import java.util.List; - -import javax.swing.*; -import javax.swing.Timer; - -import net.java.sip.communicator.impl.gui.*; -import net.java.sip.communicator.impl.gui.event.*; -import net.java.sip.communicator.impl.gui.main.call.conference.*; -import net.java.sip.communicator.impl.gui.utils.*; -import net.java.sip.communicator.impl.gui.utils.Constants; -import net.java.sip.communicator.plugin.desktoputil.*; -import net.java.sip.communicator.service.contactlist.*; -import net.java.sip.communicator.service.gui.*; -import net.java.sip.communicator.service.gui.call.*; -import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.service.protocol.media.*; -import net.java.sip.communicator.util.*; -import net.java.sip.communicator.util.Logger; -import net.java.sip.communicator.util.skin.*; - -import org.jitsi.service.neomedia.*; -import org.jitsi.util.*; -import org.jitsi.util.event.*; -import org.osgi.framework.*; - -/** - * The dialog created for a given call. - * - * Ordered buttons we are adding/removing, numbers are the index we have set. - * And the order that will be kept. - * 0 dialButton - * 1 conferenceButton - * 2 holdButton - * 3 recordButton - * 4 mergeButton - * 5 transferCallButton - * 6 localLevel - * 7 remoteLevel - * 8 desktopSharingButton - * 9 resizeVideoButton - * 10 fullScreenButton - * 11 videoButton - * 12 showHideVideoButton - * 19 chatButton - * 20 infoButton - * - * @author Yana Stamcheva - * @author Adam Netocny - * @author Lyubomir Marinov - * @author Hristo Terezov - */ -public class CallPanel - extends TransparentPanel - implements ActionListener, - PluginComponentListener, - Skinnable, - ConferencePeerViewListener, - ContactPresenceStatusListener -{ - /** - * The chat button name. - */ - private static final String CHAT_BUTTON = "CHAT_BUTTON"; - - /** - * The conference button name. - */ - private static final String CONFERENCE_BUTTON = "CONFERENCE_BUTTON"; - - /** - * The dial button name. - */ - private static final String DIAL_BUTTON = "DIAL_BUTTON"; - - /** - * The info button name. - */ - private static final String INFO_BUTTON = "INFO_BUTTON"; - - /** - * The logger for this class. - */ - private static final Logger logger = Logger.getLogger(CallDialog.class); - - /** - * The hang up button name. - */ - private static final String MERGE_BUTTON = "MERGE_BUTTON"; - - /** - * Serial version UID. - */ - private static final long serialVersionUID = 0L; - - /** - * Property to disable the info button. - */ - private static final String HIDE_CALL_INFO_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_INFO_BUTTON"; - - /** - * Property to disable the conference "add to call" button. - */ - private static final String HIDE_CONFERENCE_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_CONFERENCE_BUTTON"; - - /** - * Property to disable the record button. - */ - private static final String HIDE_CALL_RECORD_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_RECORD_BUTTON"; - - /** - * Property to disable the "call merge" button. - */ - private static final String HIDE_CALL_MERGE_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_MERGE_BUTTON"; - - /** - * Property to disable the "call merge" button. - */ - private static final String HIDE_CALL_TRANSFER_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_TRANSFER_BUTTON"; - - /** - * Property to disable the "hold" button. - */ - private static final String HIDE_CALL_HOLD_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_HOLD_BUTTON"; - - /** - * Property to disable the dial button. - */ - private static final String HIDE_DIAL_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_DIAL_BUTTON"; - - /** - * Property to disable the video button. - */ - private static final String HIDE_VIDEO_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_VIDEO_BUTTON"; - - /** - * Property to disable the button, which shows/hides participants in video - * conferences. - */ - private static final String HIDE_PEERS_LIST_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_PEERS_LIST_BUTTON"; - - /** - * Indicates if the participants list in a video conference is visible by - * default. - */ - private static final String PEERS_LIST_HIDDEN_PROP - = "net.java.sip.communicator.impl.gui.main.call.PEERS_LIST_HIDDEN"; - - /** - * Property to disable the desktop sharing button. - */ - private static final String HIDE_DESKTOP_SHARING_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_DESKTOP_SHARING_BUTTON"; - - /** - * Property to disable the full screen button. - */ - private static final String HIDE_FULL_SCREEN_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_FULL_SCREEN_BUTTON"; - - /** - * Property to disable the "show/hide local video" button. - */ - private static final String HIDE_TOGGLE_VIDEO_BUTON_PROP - = "net.java.sip.communicator.impl.gui.main.call.HIDE_TOGGLE_VIDEO_BUTTON"; - - /** - * The Component which is at the bottom of this view and contains - * {@link #settingsPanel}. It overrides the Swing-defined background on OS - * X so it needs explicit updating upon switching between full-screen and - * windowed mode in order to respect any background-related settings of the - * ancestors such as black background in full-screen mode. - */ - private JComponent bottomBar; - - /** - * The {@link CallConference} instance depicted by this CallPanel. - */ - private final CallConference callConference; - - /** - * The listener which listens to events fired by the CallConference - * depicted by this instance, the Calls participating in that - * telephony conference, the CallPeers associated with those - * Calls and the ConferenceMembers participating in the - * telephony conferences organized by those CallPeers. Updates this - * view i.e. CallPanel so that it depicts the current state of its model - * i.e. {@link #callConference}. - */ - private final CallConferenceListener callConferenceListener - = new CallConferenceListener(); - - /** - * The time in milliseconds at which the telephony call/conference depicted - * by this CallPanel (i.e. {@link #callConference}) has started. - */ - private long callConferenceStartTime; - - /** - * A timer to count call duration. - */ - private Timer callDurationTimer; - - /** - * The Frame used to display this call information statistics. - */ - private CallInfoFrame callInfoFrame; - - /** - * The panel representing the call. For conference calls this would be an - * instance of ConferenceCallPanel and for one-to-one calls this - * would be an instance of OneToOneCallPanel. - */ - private JComponent callPanel; - - /** - * Parent window. - */ - private final CallContainer callWindow; - - /** - * Chat button. - */ - private SIPCommButton chatButton; - - /** - * The operation set that will be used to update chatButton icon and - * the corresponding contact. - */ - private OperationSetPresence operationSetPresence; - - /** - * The conference button. - */ - private CallToolBarButton conferenceButton; - - /** - * The desktop sharing button. - */ - private DesktopSharingButton desktopSharingButton; - - /** - * The dial button, which opens a keypad dialog. - */ - private CallToolBarButton dialButton; - - /** - * The dial pad dialog opened when the dial button is clicked. - */ - private DialpadDialog dialpadDialog; - - /** - * The indicator which determines whether {@link #dispose()} has already - * been invoked on this instance. If true, this instance is - * considered non-functional and is to be left to the garbage collector. - */ - private boolean disposed = false; - - /** - * The handler for DTMF tones. - */ - private DTMFHandler dtmfHandler; - - /** - * The full screen button. - */ - private FullScreenButton fullScreenButton; - - /** - * HangUp button. - */ - private SIPCommButton hangupButton; - - /** - * The hold button. - */ - private HoldButton holdButton; - - /** - * Info button. - */ - private SIPCommButton infoButton; - - /** - * Indicates if the call timer has been started. - */ - private boolean isCallTimerStarted = false; - - /** - * Sound local level label. - */ - private InputVolumeControlButton localLevel; - - /** - * Merge button. - */ - private CallToolBarButton mergeButton; - - /** - * The button which allows starting and stopping the recording of - * {@link #callConference}. - */ - private RecordButton recordButton; - - /** - * Sound remote level label. - */ - private Component remoteLevel; - - /** - * The video resize button. - */ - private ResizeVideoButton resizeVideoButton; - - /** - * The panel containing call settings. - */ - private CallToolBar settingsPanel; - - /** - * The button responsible for hiding/showing the local video. - */ - private ShowHideVideoButton showHideVideoButton; - - /** - * The button, which shows / hides the participants list in a video - * conference. - */ - private ShowHidePeersButton showHidePeersButton; - - /** - * The title of this call container. - */ - private String title; - - /** - * A collection of listeners, registered for call title change events. - */ - private Collection titleListeners - = new Vector(); - - /** - * The transfer call button. - */ - private TransferCallButton transferCallButton; - - /** - * The facility which aids this instance in the dealing with the - * video-related information. - */ - private final UIVideoHandler2 uiVideoHandler; - - /** - * Indicates if this call panel should be closed immediately after hang up - * or should wait some time so that the user can be notified of the last - * state. By default we wait, so that the user can be notified of the - * current state of the call. - */ - private boolean isCloseWaitAfterHangup = true; - - /** - * The Observer which listens to {@link #uiVideoHandler} about - * changes in the video-related information. - */ - private final Observer uiVideoHandlerObserver - = new Observer() - { - public void update(Observable o, Object arg) - { - uiVideoHandlerUpdate(arg); - } - }; - - /** - * The Runnable 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(); - } - }; - - /** - * The video button. - */ - private LocalVideoButton videoButton; - - /** - * Initializes a new CallPanel which is to depict a specific - * CallConference. - * - * @param callConference the CallConference to be depicted by the - * new instance - * @param callWindow the parent window in which the new instance will be - * added - */ - public CallPanel( CallConference callConference, - CallContainer callWindow) - { - super(new BorderLayout()); - - this.callConference = callConference; - this.callWindow = callWindow; - - uiVideoHandler = new UIVideoHandler2(this.callConference); - - callDurationTimer - = new Timer( - 1000, - new ActionListener() - { - public void actionPerformed(ActionEvent e) - { - setCallTitle(callConferenceStartTime); - } - }); - callDurationTimer.setRepeats(true); - - // The call duration parameter is not known yet. - setCallTitle(0); - - initializeUserInterfaceHierarchy(); - - dtmfHandler = new DTMFHandler(this); - - /* - * Adds the listeners which will observe the model and will trigger the - * updates of this view from it. - */ - this.callConference.addCallChangeListener(callConferenceListener); - this.callConference.addCallPeerConferenceListener( - callConferenceListener); - this.callConference.addPropertyChangeListener(callConferenceListener); - uiVideoHandler.addObserver(uiVideoHandlerObserver); - - callWindow.getFrame().addPropertyChangeListener( - CallContainer.PROP_FULL_SCREEN, - callConferenceListener); - - updateViewFromModel(); - - initPluginComponents(); - } - - /** - * Handles action events. - * @param evt the ActionEvent that was triggered - */ - public void actionPerformed(ActionEvent evt) - { - JButton button = (JButton) evt.getSource(); - String buttonName = button.getName(); - - if (buttonName.equals(MERGE_BUTTON)) - { - CallManager.mergeExistingCalls( - callConference, - CallManager.getInProgressCalls()); - } - else if (buttonName.equals(DIAL_BUTTON)) - { - if (dialpadDialog == null) - dialpadDialog = this.getDialpadDialog(); - - if(!dialpadDialog.isVisible()) - { - dialpadDialog.pack(); - - Point location = new Point( button.getX(), - button.getY() + button.getHeight()); - SwingUtilities.convertPointToScreen( - location, button.getParent()); - - dialpadDialog.setLocation( - (int) location.getX() + 2, - (int) location.getY() + 2); - - dialpadDialog.addWindowFocusListener(dialpadDialog); - dialpadDialog.setVisible(true); - } - else - { - dialpadDialog.removeWindowFocusListener(dialpadDialog); - dialpadDialog.setVisible(false); - } - } - else if (buttonName.equals(CONFERENCE_BUTTON)) - { - ConferenceInviteDialog inviteDialog; - - if (callConference.isJitsiVideobridge()) - { - inviteDialog - = new ConferenceInviteDialog( - callConference, - callConference.getCalls().get(0) - .getProtocolProvider(), - true); - } - else - inviteDialog = new ConferenceInviteDialog(callConference); - - inviteDialog.setVisible(true); - } - else if (buttonName.equals(CHAT_BUTTON)) - { - /* - * If there is exactly 1 CallPeer capable of instant messaging, then - * we'll try to start a chat with her. - */ - /* - * TODO The following is very likely to block the user interface in - * a noticeable way sooner or later. - */ - List imCapableCallPeers = getIMCapableCallPeers(1); - - if (imCapableCallPeers.size() == 1) - { - Contact contact = imCapableCallPeers.get(0); - MetaContact metaContact - = GuiActivator.getContactListService() - .findMetaContactByContact(contact); - GuiActivator.getUIService().getChatWindowManager().startChat( - metaContact); - } - } - else if (buttonName.equals(INFO_BUTTON)) - { - if (callInfoFrame == null) - { - callInfoFrame = new CallInfoFrame(callConference); - addCallTitleListener(callInfoFrame); - } - callInfoFrame.setVisible( - callInfoFrame.hasCallInfo() && !callInfoFrame.isVisible()); - } - } - - /** - * Executes the action associated with the "Hang up" button which may be - * invoked by clicking the button in question or by closing this dialog. - * - * @param closeWait true to close this instance with a few seconds - * of delay or false to close it immediately - */ - public void actionPerformedOnHangupButton(boolean closeWait) - { - isCloseWaitAfterHangup = closeWait; - - this.disposeCallInfoFrame(); - - /* - * It is the responsibility of CallManager to close this CallPanel - * when a Call is ended. - */ - if (callConference.getCallCount() > 0) - CallManager.hangupCalls(callConference); - /* - * If however there are no more calls related to this panel we will - * close the window directly. This could happen in the case, where - * the other side has already hanged up the call, the call window shows - * the state disconnected and we press the hang up button. In this - * case the contained call is already null and we should only close the - * call window. - */ - else - callWindow.close(this, false); - } - - /** - * Indicates if this call panel should be closed immediately after hang up - * or should wait some time so that the user can be notified of the last - * state. - * - * @return true to indicate that when hanged up this call panel - * should not be closed immediately, false - otherwise - */ - public boolean isCloseWaitAfterHangup() - { - return isCloseWaitAfterHangup; - } - - /** - * Adds the given CallTitleListener to the list of listeners, - * notified for call title changes. - * - * @param l the CallTitleListener to add - */ - public void addCallTitleListener(CallTitleListener l) - { - synchronized (titleListeners) - { - titleListeners.add(l); - } - } - - /** - * Adds remote video specific components. - * - * @param callPeer the CallPeer - */ - public void addRemoteVideoSpecificComponents(CallPeer callPeer) - { - if(CallManager.isVideoQualityPresetSupported(callPeer)) - { - if(resizeVideoButton == null) - { - resizeVideoButton = new ResizeVideoButton(callPeer.getCall()); - resizeVideoButton.setIndex(9); - } - - if(resizeVideoButton.countAvailableOptions() > 1) - { - settingsPanel.add(resizeVideoButton); - settingsPanel.revalidate(); - settingsPanel.repaint(); - } - } - } - - /** - * Notifies this instance about a PropertyChangeEvent fired by - * {@link #callWindow}. - * - * @param ev the PropertyChangeEvent fired by callWindow - * to notify this instance about - */ - private void callWindowPropertyChange(PropertyChangeEvent ev) - { - /* - * We are registered for CallContainer#PROP_FULL_SCREEN only. This - * instance will fire the notification as its own to allow listeners to - * register with a source which is more similar to them with respect to - * life span. - */ - try - { - if (OSUtils.IS_MAC && (bottomBar != null)) - bottomBar.setOpaque(!isFullScreen()); - } - finally - { - firePropertyChange( - ev.getPropertyName(), - ev.getOldValue(), ev.getNewValue()); - } - } - - /** - * Count the number of the buttons in the supplied components. - * @param cs the components to search for buttons. - * @return number of buttons. - */ - private int countButtons(Component[] cs) - { - int count = 0; - - for(Component c : cs) - { - if(c instanceof SIPCommButton || c instanceof SIPCommToggleButton) - count++; - if(c instanceof Container) - count += countButtons(((Container)c).getComponents()); - } - - return count; - } - - /** - * Creates the bottom bar panel for this CallPanel. - * - * @return a new bottom bar panel for this CallPanel - */ - private JComponent createBottomBar() - { - bottomBar - = new TransparentPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); - bottomBar.setBorder(BorderFactory.createEmptyBorder(0, 30, 2, 30)); - - /* - * The bottomBar on OS X overrides the Swing-defined background. - * However, full-screen display usually uses a black background. The - * black background will be set elsewhere on an ancestor but we have to - * make sure that bottomBar's background does not interfere with the - * setting. - */ - if (OSUtils.IS_MAC) - { - bottomBar.setOpaque(!isFullScreen()); - bottomBar.setBackground( - new Color(GuiActivator.getResources().getColor( - "service.gui.MAC_PANEL_BACKGROUND"))); - } - - bottomBar.add(settingsPanel); - - return bottomBar; - } - - /** - * Releases the resources acquired by this instance which require explicit - * disposal (e.g. any listeners added to the depicted - * CallConference, the participating Calls, and their - * associated CallPeers). Invoked by CallManager when it - * determines that this CallPanel is no longer necessary. - */ - void dispose() - { - disposed = true; - - callConference.removeCallChangeListener(callConferenceListener); - callConference.removeCallPeerConferenceListener(callConferenceListener); - callConference.removePropertyChangeListener(callConferenceListener); - - uiVideoHandler.deleteObserver(uiVideoHandlerObserver); - uiVideoHandler.dispose(); - - callWindow.getFrame().removePropertyChangeListener( - CallContainer.PROP_FULL_SCREEN, - callConferenceListener); - - if (callPanel != null) - { - if(callPanel instanceof BasicConferenceCallPanel) - { - ((BasicConferenceCallPanel) callPanel) - .removePeerViewListener(this); - } - ((CallRenderer) callPanel).dispose(); - } - - // clears the contact status listener - if(operationSetPresence != null) - { - operationSetPresence.removeContactPresenceStatusListener(this); - } - } - - /** - * Disposes the call info frame if it exists. - */ - public void disposeCallInfoFrame() - { - if (callInfoFrame != null) - callInfoFrame.dispose(); - } - - /** - * Updates {@link #settingsPanel} from the model of this view. The update is - * performed in the AWT event dispatching thread. - *

- * The center of this view is occupied by {@link #callPanel}, the bottom of - * this view is dedicated to settingsPanel. The method - * {@link #updateViewFromModelInEventDispatchThread()} updates - * callPanel from the model of this view and then invokes the - * method updateSettingsPanelInEventDispatchThread(). Thus this - * whole view is updated so that it depicts the current state of its model. - *

- * - * @param callConferenceIsEnded true if the method - * updateViewFromModelInEventDispatchThread() considers the - * {@link #callConference} ended; otherwise, false. When the - * callConference is considered ended, the callPanel - * instance will not be switched to a specific type (one-to-one, audio-only, - * or audio/video) because, otherwise, the switch will leave it - * null and this view will remain blank. In such a case, - * settingsPanel may wish to do pretty much the same but disable - * and/or hide the buttons it contains. - */ - private void doUpdateSettingsPanelInEventDispatchThread( - boolean callConferenceIsEnded) - { - settingsPanel.setFullScreen(isFullScreen()); - - boolean isConference = (callPanel instanceof BasicConferenceCallPanel); - - /* - * For whatever reason, we're treating the localLevel and the - * remoteLevel buttons differently and we're adding and removing them in - * accord with the conference state of the user interface. - */ - if (isConference) - { - settingsPanel.add(localLevel); - settingsPanel.add(remoteLevel); - } - else - { - settingsPanel.remove(localLevel); - settingsPanel.remove(remoteLevel); - } - - /* - * We do not support chat conferencing with the participants in a - * telephony conference at this time so we do not want the chatButton - * visible in such a scenario. - */ - List imContacts = getIMCapableCallPeers(1); - chatButton.setVisible( - !isConference && (imContacts.size() == 1)); - if(chatButton.isVisible() && operationSetPresence == null) - { - Contact contact = imContacts.get(0); - operationSetPresence = - contact.getProtocolProvider() - .getOperationSet(OperationSetPresence.class); - if(operationSetPresence != null) - operationSetPresence.addContactPresenceStatusListener(this); - - chatButton.setIconImage( - Constants.getMessageStatusIcon(contact.getPresenceStatus())); - chatButton.repaint(); - } - - updateHoldButtonState(); - updateMergeButtonState(); - - List calls = callConference.getCalls(); - /* - * OperationSetAdvancedTelephony implements call transfer. The feature - * is not supported if the local user/peer is a conference focus. - * Instead of disabling the transferCallButton in this case though, we - * want it hidden. - */ - boolean advancedTelephony = !calls.isEmpty(); - boolean telephonyConferencing = false; - boolean videoTelephony = false; - boolean videoTelephonyIsLocalVideoAllowed = false; - boolean videoTelephonyIsLocalVideoStreaming = false; - boolean desktopSharing = false; - boolean desktopSharingIsStreamed = false; - boolean allCallsConnected = true; - - for (Call call : calls) - { - ProtocolProviderService pps = call.getProtocolProvider(); - - /* - * The transferCallButton requires OperationSetAdvancedTelephony - * for all Calls. - */ - if (advancedTelephony) - { - OperationSetAdvancedTelephony osat - = pps.getOperationSet(OperationSetAdvancedTelephony.class); - - if (osat == null) - advancedTelephony = false; - } - - /* - * The conferenceButton needs at least one Call with - * OperationSetTelephonyConferencing, - */ - if (!telephonyConferencing) - { - OperationSetTelephonyConferencing ostc - = pps.getOperationSet( - OperationSetTelephonyConferencing.class); - - if (ostc != null) - telephonyConferencing = true; - } - - if (!videoTelephony - || !videoTelephonyIsLocalVideoAllowed - || !videoTelephonyIsLocalVideoStreaming) - { - OperationSetVideoTelephony osvt - = pps.getOperationSet(OperationSetVideoTelephony.class); - - if (osvt != null) - { - if (!videoTelephony) - videoTelephony = true; - if (!videoTelephonyIsLocalVideoAllowed - && osvt.isLocalVideoAllowed(call)) - videoTelephonyIsLocalVideoAllowed = true; - if (!videoTelephonyIsLocalVideoStreaming - && osvt.isLocalVideoStreaming(call)) - videoTelephonyIsLocalVideoStreaming = true; - } - } - - if(!desktopSharing) - { - OperationSetDesktopStreaming osds - = pps.getOperationSet( - OperationSetDesktopStreaming.class); - if(osds != null) - { - desktopSharing = true; - - if(videoTelephonyIsLocalVideoStreaming - && call instanceof MediaAwareCall - && ((MediaAwareCall) call).getMediaUseCase() - == MediaUseCase.DESKTOP) - { - desktopSharingIsStreamed = true; - } - } - } - - if (CallState.CALL_IN_PROGRESS != call.getCallState()) - { - allCallsConnected = false; - } - } - - if(conferenceButton != null) - conferenceButton.setEnabled(telephonyConferencing); - - if(transferCallButton != null) - { - transferCallButton.setEnabled(advancedTelephony); - transferCallButton.setVisible(!callConference.isConferenceFocus()); - } - - /* - * The videoButton is a beast of its own kind because it depends not - * only on the state of the depicted telephony conference but also on - * the global application state. - */ - if(videoButton != null) - { - videoButton.setEnabled(allCallsConnected && videoTelephony); - videoButton.setSelected(videoTelephonyIsLocalVideoAllowed); - - /* - * Consequently, the showHideVideoButton which depends on videoButton - * has to be updated depending on the state of the videoButton as well. - */ - if(showHideVideoButton != null) - { - showHideVideoButton.setEnabled( - videoButton.isEnabled() - && videoTelephonyIsLocalVideoAllowed); - showHideVideoButton.setSelected( - showHideVideoButton.isEnabled() - && uiVideoHandler.isLocalVideoVisible()); - showHideVideoButton.setVisible(showHideVideoButton.isEnabled()); - } - } - - if (showHidePeersButton != null) - { - showHidePeersButton.setVisible(isConference - && CallManager.isVideoStreaming(callConference)); - } - - // The desktop sharing button depends on the operation set desktop - // sharing server. - if(desktopSharingButton != null) - { - desktopSharingButton.setEnabled(desktopSharing); - desktopSharingButton.setSelected(desktopSharingIsStreamed); - } - - if (callPanel instanceof OneToOneCallPanel) - { - OneToOneCallPanel oneToOneCallPanel = (OneToOneCallPanel) callPanel; - if(desktopSharingIsStreamed) - oneToOneCallPanel.addDesktopSharingComponents(); - else - oneToOneCallPanel.removeDesktopSharingComponents(); - } - } - - /** - * Updates this view i.e. CallPanel so that it depicts the current - * state of its model i.e. callConference. The update is performed - * in the AWT event dispatching thread. - */ - private void doUpdateViewFromModelInEventDispatchThread() - { - /* - * If the telephony conference depicted by this instance has ended, do - * not update the user interface because it will be left blank. It is - * CallManager's responsibility to dispose of this CallPanel after its - * telephony conference has ended. Additionally, the various types of - * callPanel will usually require at least one CallPeer in order to not - * be blank. The absence of CallPeers usually indicates that a Call and, - * respectively, a telephony conference has ended. So it makes some - * sense to skip the update in such cases in order to try to not have - * the user interface blank. - */ - if (callConference.isEnded() - || (callConference.getCallPeerCount() == 0)) - { - /* - * However, the settingsPanel contains buttons which may still need - * to be disabled and/or hidden. - */ - updateSettingsPanelInEventDispatchThread(true); - return; - } - - boolean isConference = isConference(); - boolean isVideo = CallManager.isVideoStreaming(callConference); - CallPeer callPeer = null; - boolean validateAndRepaint = false; - - if (callPanel != null) - { - boolean removeCallPanel; - - if (isConference) - { - if (callPanel instanceof BasicConferenceCallPanel) - { - if (isVideo) - { - removeCallPanel - = !(callPanel instanceof VideoConferenceCallPanel); - } - else - { - removeCallPanel - = (callPanel instanceof VideoConferenceCallPanel); - } - } - else - { - removeCallPanel = true; - } - } - else - { - if (callPanel instanceof OneToOneCallPanel) - { - if (callPeer == null) - { - List callPeers - = callConference.getCallPeers(); - - if (!callPeers.isEmpty()) - callPeer = callPeers.get(0); - } - removeCallPanel - = !((OneToOneCallPanel) callPanel).getCallPeer().equals( - callPeer); - } - else - { - if( (callPanel instanceof BasicConferenceCallPanel) && - ((BasicConferenceCallPanel) callPanel) - .hasDelayedCallPeers()) - { - removeCallPanel = false; - } - else - { - removeCallPanel = true; - } - - } - } - if (removeCallPanel) - { - remove(callPanel); - validateAndRepaint = true; - try - { - ((CallRenderer) callPanel).dispose(); - } - finally - { - callPanel = null; - } - } - } - if (callPanel == null) - { - if (isConference) - { - if (isVideo) - { - callPanel - = new VideoConferenceCallPanel( - this, - callConference, - uiVideoHandler); - } - else - { - callPanel - = new AudioConferenceCallPanel(this, callConference); - } - - ((BasicConferenceCallPanel) callPanel) - .addPeerViewlListener(this); - } - else - { - if (callPeer == null) - { - List callPeers = callConference.getCallPeers(); - - if (!callPeers.isEmpty()) - callPeer = callPeers.get(0); - } - if (callPeer != null) - { - callPanel - = new OneToOneCallPanel(this, callPeer, uiVideoHandler); - } - } - if (callPanel != null) - { - add(callPanel, BorderLayout.CENTER); - validateAndRepaint = true; - } - } - - try - { - /* - * The center of this view is occupied by callPanel and we have just - * updated it. The bottom of this view is dedicated to settingsPanel - * so we have to update it as well. - */ - updateSettingsPanelInEventDispatchThread(false); - } - finally - { - /* - * It seems that AWT/Swing does not validate and/or repaint this - * Container (enough) and, consequently, its display may not update - * itself with an up-to-date drawing of the current callPanel. - */ - if (validateAndRepaint) - { - if (isDisplayable()) - { - validate(); - repaint(); - } - else - doLayout(); - } - } - } - - /** - * Attempts to give a specific Component a visible rectangle with a - * specific width and a specific height if possible and sane by resizing - * the Window which contains this instance. - * - * @param component the Component which requests a visible - * rectangle with the specified width and height - * @param width the width of the visible rectangle requested by the - * specified component - * @param height the height of the visible rectangle requested by the - * specified component - */ - private void ensureSize(Component component, int width, int height) - { - CallContainer callContainer = getCallWindow(); - - if (callContainer != null) - callContainer.ensureSize(component, width, height); - } - - /** - * Notifies interested listeners of a call title change. - */ - private void fireTitleChangeEvent() - { - Iterator listeners; - - synchronized (titleListeners) - { - listeners = new Vector(titleListeners).iterator(); - } - - while (listeners.hasNext()) - { - listeners.next().callTitleChanged(this); - } - } - - /** - * Returns the CallConference depicted by this CallPanel - * - * @return the CallConference depicted by this - * CallConference - */ - public CallConference getCallConference() - { - return callConference; - } - - /** - * Returns the initial call title. The call title could be then changed by - * call setCallTitle. - * - * @return the call title - */ - public String getCallTitle() - { - return title; - } - - /** - * Returns the parent call window. - * - * @return the parent call window - */ - public CallContainer getCallWindow() - { - return callWindow; - } - - /** - * Returns the currently used CallRenderer. - * @return the currently used CallRenderer - */ - public CallRenderer getCurrentCallRenderer() - { - return (CallRenderer) callPanel; - } - - /** - * Returns the DialpadDialog corresponding to this CallDialog. - * - * @return the DialpadDialog corresponding to this CallDialog. - */ - private DialpadDialog getDialpadDialog() - { - return new DialpadDialog(dtmfHandler); - } - - /** - * Finds the Contacts which are participating in the telephony - * conference depicted by this instance and which are capable of instant - * messaging i.e. support {@link OperationSetBasicTelephony}. - * - * @param limit the maximum number of Contacts to be found. Since - * it is expensive in terms of execution time (at very least) to find a - * Contact which stands for a CallPeer (and to query it - * whether it supports instant messaging), it is advised to limit the search - * as much as possible. For example, the chatButton is enabled - * and/or shown only when there is exactly one such Contact so it - * makes perfect sense to specify 1 as the limit in the - * case. - * @return a List of the Contacts which are participating - * in the telephony conference depicted by this instance and which are - * capable of instant messaging i.e. support - * OperationSetBasicTelephony - */ - private List getIMCapableCallPeers(int limit) - { - List callPeers = callConference.getCallPeers(); - List contacts = new ArrayList(callPeers.size()); - - /* - * Choose the CallPeers (or rather their associated Contacts) which are - * capable of basic instant messaging. - */ - for (CallPeer callPeer : callPeers) - { - if (callPeer.getProtocolProvider().getOperationSet( - OperationSetBasicInstantMessaging.class) - != null) - { - /* - * CallPeer#getContact) is more expensive in terms of execution - * than ProtocolProviderService#getOperationSet(Class). - */ - Contact contact = callPeer.getContact(); - - if (contact != null) - contacts.add(contact); - } - else - { - Contact contact = CallManager.getIMCapableCusaxContact(callPeer); - if (contact != null) - { - contacts.add(contact); - } - } - - if (contacts.size() >= limit) - break; - } - return contacts; - } - - /** - * Returns the minimum width needed to show buttons. - * Used to calculate the minimum size of the call dialog. - * @return the minimum width for the buttons. - */ - public int getMinimumButtonWidth() - { - int numberOfButtons = countButtons(settingsPanel.getComponents()); - - if (numberOfButtons > 0) - { - // +1 cause we had and a hangup button - // *32, a button is 28 pixels width and give some border - return (numberOfButtons + 1) * 32; - } - else - return -1; - } - - /** - * Initializes buttons order in the call tool bar. - */ - private void initButtonIndexes() - { - if (dialButton != null) - dialButton.setIndex(0); - if (conferenceButton != null) - conferenceButton.setIndex(1); - if (holdButton != null) - holdButton.setIndex(2); - if (recordButton != null) - recordButton.setIndex(3); - if (mergeButton != null) - mergeButton.setIndex(4); - if (transferCallButton != null) - transferCallButton.setIndex(5); - - localLevel.setIndex(6); - if (remoteLevel instanceof OrderedComponent) - ((OrderedComponent) remoteLevel).setIndex(7); - - if (desktopSharingButton != null) - desktopSharingButton.setIndex(8); - - if (fullScreenButton != null) - fullScreenButton.setIndex(10); - - if (videoButton != null) - videoButton.setIndex(11); - if (showHideVideoButton != null) - showHideVideoButton.setIndex(12); - if (showHidePeersButton != null) - showHidePeersButton.setIndex(13); - - chatButton.setIndex(19); - - if (infoButton != null) - infoButton.setIndex(20); - - hangupButton.setIndex(100); - } - - /** - * Initialize plug-in components already registered for this container. - */ - private void initPluginComponents() - { - // Search for plug-in components registered through the OSGI - // BundleContext. - - String osgiFilter - = "(" - + net.java.sip.communicator.service.gui.Container.CONTAINER_ID - + "=" - + net.java.sip.communicator.service.gui.Container - .CONTAINER_CALL_DIALOG.getID() - + ")"; - ServiceReference[] serRefs = null; - - try - { - serRefs - = GuiActivator.bundleContext.getServiceReferences( - PluginComponentFactory.class.getName(), - osgiFilter); - } - catch (InvalidSyntaxException ise) - { - logger.error("Could not obtain plugin reference.", ise); - } - - if (serRefs != null) - { - for (ServiceReference serRef : serRefs) - { - PluginComponentFactory factory - = (PluginComponentFactory) - GuiActivator.bundleContext.getService(serRef); - - PluginComponent component = - factory.getPluginComponentInstance(CallPanel.this); - - component.setCurrentContact( - CallManager.getPeerMetaContact( - callConference.getCallPeers().get(0))); - - settingsPanel.add((Component) component.getComponent()); - } - } - - GuiActivator.getUIService().addPluginComponentListener(this); - } - - /** - * Initializes the user interface hierarchy of this CallPanel i.e. - * the AWT Components which constitute the user interface to be - * displayed by this Component. Their state does not have to depict - * the current state of the model of this view because - * {@link #updateViewFromModel()} will be invoked before this view becomes - * visible. At the center of the user interface of this view is - * {@link #callPanel} but it is dynamically added and removed multiple times - * as part of the execution of the updateViewFromModel method so - * it is not dealt with here. - */ - private void initializeUserInterfaceHierarchy() - { - /* - * The settingsPanel will contain the buttons. It is initialized before - * the buttons in case any of the buttons need it (which is hard to - * determine at the time of this writing). - */ - settingsPanel = new CallToolBar(isFullScreen(), false); - - /* - * TODO CallPanel depicts a whole CallConference which may have multiple - * Calls, new Calls may be added to the CallConference and existing - * Calls may be removed from the CallConference. For example, the - * buttons which accept a Call as an argument should be changed to take - * into account the whole CallConference. - */ - Call aCall = callConference.getCalls().get(0); - - chatButton - = new CallToolBarButton( - ImageLoader.getImage(ImageLoader.CHAT_BUTTON_SMALL_WHITE), - CHAT_BUTTON, - GuiActivator.getResources().getI18NString( - "service.gui.CHAT")); - - if(isButtonEnabled(HIDE_CONFERENCE_BUTON_PROP)) - { - conferenceButton - = new CallToolBarButton( - ImageLoader.getImage(ImageLoader.ADD_TO_CALL_BUTTON), - CONFERENCE_BUTTON, - GuiActivator.getResources().getI18NString( - "service.gui.CREATE_CONFERENCE_CALL")); - } - - if(isButtonEnabled(HIDE_DESKTOP_SHARING_BUTON_PROP)) - { - desktopSharingButton = new DesktopSharingButton(aCall); - } - - if(isButtonEnabled(HIDE_DIAL_BUTON_PROP)) - { - dialButton - = new CallToolBarButton( - ImageLoader.getImage(ImageLoader.DIAL_BUTTON), - DIAL_BUTTON, - GuiActivator.getResources().getI18NString( - "service.gui.DIALPAD")); - } - - if(isButtonEnabled(HIDE_FULL_SCREEN_BUTON_PROP)) - { - fullScreenButton = new FullScreenButton(this); - } - - hangupButton = new HangupButton(this); - - if(isButtonEnabled(HIDE_CALL_HOLD_BUTON_PROP)) - { - holdButton = new HoldButton(aCall); - } - - if(isButtonEnabled(HIDE_CALL_INFO_BUTON_PROP)) - { - infoButton - = new CallToolBarButton( - ImageLoader.getImage(ImageLoader.CALL_INFO), - INFO_BUTTON, - GuiActivator.getResources().getI18NString( - "service.gui.PRESS_FOR_CALL_INFO")); - } - - if(isButtonEnabled(HIDE_CALL_MERGE_BUTON_PROP)) - { - mergeButton - = new CallToolBarButton( - ImageLoader.getImage(ImageLoader.MERGE_CALL_BUTTON), - MERGE_BUTTON, - GuiActivator.getResources().getI18NString( - "service.gui.MERGE_TO_CALL")); - - } - - if(isButtonEnabled(HIDE_CALL_RECORD_BUTON_PROP)) - { - recordButton = new RecordButton(aCall); - } - - if(isButtonEnabled(HIDE_TOGGLE_VIDEO_BUTON_PROP)) - { - showHideVideoButton = new ShowHideVideoButton(uiVideoHandler); - } - - if(isButtonEnabled(HIDE_CALL_TRANSFER_BUTON_PROP)) - { - transferCallButton = new TransferCallButton(aCall); - } - - if(isButtonEnabled(HIDE_VIDEO_BUTON_PROP)) - { - videoButton = new LocalVideoButton(aCall); - } - - if (isButtonEnabled(HIDE_PEERS_LIST_BUTON_PROP)) - { - // If the PEERS_LIST_HIDDEN_PROP isn't specified we show the list - // by default. - showHidePeersButton = new ShowHidePeersButton(this, - isButtonEnabled(PEERS_LIST_HIDDEN_PROP)); - } - - localLevel - = new InputVolumeControlButton( - aCall, - ImageLoader.MICROPHONE, - ImageLoader.MUTE_BUTTON, - true, - false); - remoteLevel - = new OutputVolumeControlButton( - callConference, - ImageLoader.VOLUME_CONTROL_BUTTON, - false, - true) - .getComponent(); - - /* - * Now that the buttons have been initialized, set their order indexes - * so that they get added in the correct order later on. - */ - initButtonIndexes(); - - chatButton.addActionListener(this); - if (conferenceButton != null) - conferenceButton.addActionListener(this); - if (dialButton != null) - dialButton.addActionListener(this); - if (infoButton != null) - infoButton.addActionListener(this); - if (mergeButton != null) - mergeButton.addActionListener(this); - - settingsPanel.add(chatButton); - if (conferenceButton != null) - settingsPanel.add(conferenceButton); - if (desktopSharingButton != null) - settingsPanel.add(desktopSharingButton); - if (dialButton != null) - settingsPanel.add(dialButton); - if (fullScreenButton != null) - settingsPanel.add(fullScreenButton); - - settingsPanel.add(hangupButton); - - if (holdButton != null) - settingsPanel.add(holdButton); - if (infoButton != null) - settingsPanel.add(infoButton); - if (mergeButton != null) - settingsPanel.add(mergeButton); - if (recordButton != null) - settingsPanel.add(recordButton); - if (showHideVideoButton != null) - settingsPanel.add(showHideVideoButton); - if (mergeButton != null) - settingsPanel.add(transferCallButton); - if (videoButton != null) - settingsPanel.add(videoButton); - if (showHidePeersButton != null) - settingsPanel.add(showHidePeersButton); - - // The bottom bar will contain the settingsPanel. - add(createBottomBar(), BorderLayout.SOUTH); - } - - /** - * Tests a provided boolean property name, returning false if it should be - * hidden. - * - * Used in {@link #initializeUserInterfaceHierarchy()} - * @param buttonHidePropertyName the name of the boolean property to check. - * @return false if the button should be hidden, true otherwise. - * - */ - private boolean isButtonEnabled(String buttonHidePropertyName) - { - return !GuiActivator.getConfigurationService().getBoolean( - buttonHidePropertyName, - false); - } - - /** - * Returns true if the call timer has been started, otherwise - * returns false. - * @return true if the call timer has been started, otherwise - * returns false - */ - public boolean isCallTimerStarted() - { - return isCallTimerStarted; - } - - /** - * Checks if the contained call is a conference call. - * - * @return true if the contained Call is a conference - * call, otherwise - returns false. - */ - boolean isConference() - { - // If we're the focus of the conference. - if (callConference.isConferenceFocus()) - return true; - - // If one of our peers is a conference focus, we're in a - // conference call. - List callPeers = callConference.getCallPeers(); - - for (CallPeer callPeer : callPeers) - { - if (callPeer.isConferenceFocus()) - return true; - } - - // the call can have two peers at the same time and there is no one - // is conference focus. This is situation when someone has made an - // attended transfer and has transfered us. We have one call with two - // peers the one we are talking to and the one we have been transfered - // to. And the first one is been hanged up and so the call passes through - // conference call focus a moment and than go again to one to one call. - return callPeers.size() > 1; - } - - /** - * Determines whether this view is displayed in full-screen or windowed - * mode. - * - * @return true if this view is displayed in full-screen mode or - * false for windowed mode - */ - boolean isFullScreen() - { - return callWindow.isFullScreen(); - } - - /** - * Checks whether recording is currently enabled or not, state retrieved - * from call record button state. - * - * @return true if the recording is already started, false - * otherwise - */ - public boolean isRecordingStarted() - { - if (recordButton == null) - return false; - - return recordButton.isSelected(); - } - - /** - * Returns true if the show/hide video button is currently selected, - * false - otherwise. - * - * @return true if the show/hide video button is currently selected, - * false - otherwise - */ - public boolean isShowHideVideoButtonSelected() - { - return showHideVideoButton.isSelected(); - } - - /** - * Reloads icons. - */ - public void loadSkin() - { - if (dialButton != null) - { - dialButton.setBackgroundImage( - ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG)); - dialButton.setIconImage( - ImageLoader.getImage(ImageLoader.DIAL_BUTTON)); - } - - if (conferenceButton != null) - { - conferenceButton.setBackgroundImage( - ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG)); - conferenceButton.setIconImage( - ImageLoader.getImage(ImageLoader.ADD_TO_CALL_BUTTON)); - } - - if (hangupButton != null) - hangupButton.setBackgroundImage( - ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_BG)); - } - - /** - * Notifies this instance about a specific VideoEvent which may - * warrant {@link #ensureSize(Component, int, int)} to be invoked in order - * to try to have the associated visual Component displaying video - * shown without scaling. The method will execute on the AWT event - * dispatching thread because it will be making its judgments based on the - * properties of AWT Components. - * - * @param ev a VideoEvent which represents the cause of the - * notification and specifies the visual Component displaying video - * which may need an adjustment of a Frame's size in order to be displayed - * without scaling - */ - private void maybeEnsureSize(final VideoEvent ev) - { - if (!SwingUtilities.isEventDispatchThread()) - { - SwingUtilities.invokeLater( - new Runnable() - { - public void run() - { - maybeEnsureSize(ev); - } - }); - return; - } - - if (ev instanceof SizeChangeVideoEvent) - { - /* - * If a visual Component depicting video (streaming between the - * local peer/user and the remote peers) changes its size, try to - * adjust the size of the Frame which displays it so that it appears - * without scaling. - */ - SizeChangeVideoEvent scev = (SizeChangeVideoEvent) ev; - - ensureSize( - scev.getVisualComponent(), - scev.getWidth(), scev.getHeight()); - } - else if (ev.getType() == VideoEvent.VIDEO_ADDED) - { - Component video = ev.getVisualComponent(); - - if ((video != null) - && UIVideoHandler2.isAncestor(this, video) - && video.isPreferredSizeSet()) - { - Dimension prefSize = video.getPreferredSize(); - - if ((prefSize.height > 0) && (prefSize.width > 0)) - { - Dimension size = video.getSize(); - - if ((prefSize.height > size.height) - || (prefSize.width > size.width)) - { - ensureSize( - video, - prefSize.width, prefSize.height); - } - } - } - } - } - - /** - * Invoked by {@link #callConferenceListener} to notify this instance about - * an EventObject related to the CallConference depicted - * by this CallPanel, the Calls participating in it, - * the CallPeers associated with them, the - * ConferenceMembers participating in any telephony conferences - * organized by them, etc. In other words, notifies this instance about - * any change which may cause an update to be required so that this view - * i.e. CallPanel depicts the current state of its model i.e. - * {@link #callConference}. - * - * @param ev the EventObject this instance is being notified - * about. - */ - private void onCallConferenceEventObject(EventObject ev) - { - /* - * The main task is to invoke updateViewFromModel() in order to make - * sure that this view depicts the current state of its model. - */ - - try - { - /* - * However, we seem to be keeping track of the duration of the call - * (i.e. the telephony conference) in the user interface. Stop the - * Timer which ticks the duration of the call as soon as the - * telephony conference depicted by this instance appears to have - * ended. The situation will very likely occur when a Call is - * removed from the telephony conference or a CallPeer is removed - * from a Call. - */ - boolean tryStopCallTimer = false; - - if (ev instanceof CallPeerEvent) - { - tryStopCallTimer - = (CallPeerEvent.CALL_PEER_REMOVED - == ((CallPeerEvent) ev).getEventID()); - } - else if (ev instanceof PropertyChangeEvent) - { - PropertyChangeEvent pcev = (PropertyChangeEvent) ev; - - tryStopCallTimer - = (CallConference.CALLS.equals(pcev.getPropertyName()) - && (pcev.getOldValue() instanceof Call) - && (pcev.getNewValue() == null)); - } - if (tryStopCallTimer - && (callConference.isEnded() - || callConference.getCallPeerCount() == 0)) - { - stopCallTimer(); - } - } - finally - { - updateViewFromModel(); - } - } - - /** - * Notifies this CallPanel about a specific CallEvent - * (received by CallManager). The source Call may or may - * not be participating in the telephony conference depicted by this - * instance but allows it to update any state which may depend on the - * Calls which are established application-wide. - * - * @param ev a CallEvent which specifies the Call which - * caused this instance to be notified and the exact type of the - * notification event - */ - void onCallEvent(CallEvent ev) - { - updateMergeButtonState(); - } - - /** - * Adds/removes the Component of the PluginComponent - * specified by a PluginComponentEvent to/from - * {@link #settingsPanel} (if it is appropriate for this - * Container). - * - * @param ev a PluginComponentEvent which specifies the - * PluginComponent whose Component is to be added/removed - * to/from {@link #settingsPanel} - */ - protected void onPluginComponentEvent(PluginComponentEvent ev) - { - PluginComponentFactory pc = ev.getPluginComponentFactory(); - - if (pc.getContainer().equals( - net.java.sip.communicator.service.gui.Container - .CONTAINER_CALL_DIALOG)) - { - PluginComponent plugin = - pc.getPluginComponentInstance(CallPanel.this); - Component c = (Component)plugin.getComponent(); - plugin.setCurrentContact( - CallManager.getPeerMetaContact( - callConference.getCallPeers().get(0))); - - switch (ev.getEventID()) - { - case PluginComponentEvent.PLUGIN_COMPONENT_ADDED: - settingsPanel.add(c); - break; - case PluginComponentEvent.PLUGIN_COMPONENT_REMOVED: - settingsPanel.remove(c); - break; - } - - settingsPanel.revalidate(); - settingsPanel.repaint(); - } - } - - /** - * Indicates that the peer panel was added. - * - * @param ev the event. - */ - public void peerViewAdded(ConferencePeerViewEvent ev) {} - - /** - * Indicates that the peer panel was removed. - * - * @param ev the event. - */ - public void peerViewRemoved(ConferencePeerViewEvent ev) - { - updateViewFromModel(); - } - - /** - * {@inheritDoc} - * - * Adds the Component of the PluginComponent specified by - * the PluginComponentEvent to {@link #settingsPanel} (if it is - * appropriate for this Container). - */ - public void pluginComponentAdded(PluginComponentEvent ev) - { - onPluginComponentEvent(ev); - } - - /** - * {@inheritDoc} - * - * Removes the Component of the PluginComponent specified - * by the PluginComponentEvent from {@link #settingsPanel} (if it - * is appropriate for this Container). - */ - public void pluginComponentRemoved(PluginComponentEvent ev) - { - onPluginComponentEvent(ev); - } - - /** - * Removes the given CallTitleListener to the list of listeners, - * notified for call title changes. - * - * @param l the CallTitleListener to remove - */ - public void removeCallTitleListener(CallTitleListener l) - { - synchronized (titleListeners) - { - titleListeners.remove(l); - } - } - - /** - * Remove remote video specific components. - */ - public void removeRemoteVideoSpecificComponents() - { - if(resizeVideoButton != null) - settingsPanel.remove(resizeVideoButton); - - settingsPanel.revalidate(); - settingsPanel.repaint(); - } - - /** - * Sets the title of this dialog in accord with a specific time of start of - * the telephony call/conference depicted by this CallPanel. - * - * @param startTime the time in milliseconds at which the telephony - * call/conference depicted by this CallPanel is considered to have - * started - */ - private void setCallTitle(long startTime) - { - StringBuilder title = new StringBuilder(); - - if (startTime != 0) - { - title.append( - GuiUtils.formatTime( - startTime, - System.currentTimeMillis())); - title.append(" | "); - } - else - title.append("00:00:00 | "); - - List callPeers = callConference.getCallPeers(); - - if ((callPeers.size() > 0) - && (GuiActivator.getUIService().getSingleWindowContainer() - != null)) - { - title.append(callPeers.get(0).getDisplayName()); - } - else - { - title.append( - GuiActivator.getResources().getI18NString( - "service.gui.CALL")); - } - - this.title = title.toString(); - - fireTitleChangeEvent(); - } - - /** - * Sets the display of this view to full-screen or windowed mode. - * - * @param fullScreen true to display this view in full-screen mode - * or false for windowed mode - */ - void setFullScreen(boolean fullScreen) - { - callWindow.setFullScreen(fullScreen); - } - - /** - * Shows/hides the thumbnails list in the case of video conference. - * - * @param show true to show the thumbnails list, false - * to hide it - */ - public void showThumbnailsList(boolean show) - { - // This shouldn't happen, but if we aren't in a video conference we - // have nothing to do here. - if (!(callPanel instanceof VideoConferenceCallPanel)) - return; - - ((VideoConferenceCallPanel) callPanel).showThumbnailsList(show); - } - - /** - * Selects or unselects the video button in this call dialog. - * - * @param isSelected indicates if the video button should be selected or not - */ - public void setVideoButtonSelected(boolean isSelected) - { - if (isSelected && !videoButton.isSelected()) - videoButton.setSelected(true); - else if (!isSelected && videoButton.isSelected()) - videoButton.setSelected(false); - } - - /** - * Starts the timer that counts call duration. - */ - public void startCallTimer() - { - callConferenceStartTime = System.currentTimeMillis(); - callDurationTimer.start(); - isCallTimerStarted = true; - } - - /** - * Stops the timer that counts call duration. - */ - public void stopCallTimer() - { - this.callDurationTimer.stop(); - } - - /** - * Notifies this instance that {@link #uiVideoHandler} has reported a change - * in the video-related information which may warrant an update of this view - * from its model. - * - * @param arg an Object, if any, which represents the cause that - * triggered the notification - */ - private void uiVideoHandlerUpdate(Object arg) - { - /* The most important task is to update this view from its model. */ - - /* - * If a visual Component displaying video is reported to have been - * added/prepared/received, we may have to adjust the size of the Frame - * displaying this user interface so that the video appears without - * scaling. - */ - /* - * XXX The following may be making judgments about the user interface - * out of the AWT event dispatching thread which is a prerequisite for - * unexpected behavior. Anyway, that's the only idea at the time of this - * writing. - */ - VideoEvent maybeEnsureSize = null; - - if (arg instanceof VideoEvent) - { - try - { - VideoEvent vev = (VideoEvent) arg; - int vevType = vev.getType(); - - if (vevType == VideoEvent.VIDEO_ADDED) - { - Component video = vev.getVisualComponent(); - - if ((video != null) - && !UIVideoHandler2.isAncestor(this, video)) - { - maybeEnsureSize = vev; - } - } - else if (vevType == SizeChangeVideoEvent.VIDEO_SIZE_CHANGE) - { - /* - * If a visual Component depicting video (streaming between - * the local peer/user and the remote peers) changes its - * size, try to adjust the size of the Frame which displays - * it so that it appears without scaling. - */ - maybeEnsureSize = vev; - } - } - catch (Throwable t) - { - if (t instanceof ThreadDeath) - throw (ThreadDeath) t; - else if (logger.isDebugEnabled()) - { - logger.debug( - "Failed to determine whether it is necessary to" - + " adjust a Frame's size in response to a" - + " VideoEvent.", - t); - } - } - } - - updateViewFromModel(); - - if (maybeEnsureSize != null) - { - /* - * The method maybeEnsureSize will execute on the AWT event - * dispatching thread. - */ - try - { - maybeEnsureSize(maybeEnsureSize); - } - catch (Throwable t) - { - if (t instanceof ThreadDeath) - throw (ThreadDeath) t; - else - { - logger.error( - "Failed to adjust a Frame's size" - + " in response to a VideoEvent.", - t); - } - } - } - } - - /** - * Updates the state of the general hold button. The hold button is selected - * only if all call peers are locally or mutually on hold at the same time. - * In all other cases the hold button is unselected. - */ - public void updateHoldButtonState() - { - // If the hold button has been disabled by its configuration property we - // have nothing more to do here. - if (holdButton == null) - return; - - if(!SwingUtilities.isEventDispatchThread()) - { - SwingUtilities.invokeLater(new Runnable() - { - public void run() - { - updateHoldButtonState(); - } - }); - - return; - } - - List peers = callConference.getCallPeers(); - boolean areAllPeersLocallyOnHold; - - if (peers.isEmpty()) - { - /* - * It feels natural to not have the holdButton selected when there - * are no peers. - */ - areAllPeersLocallyOnHold = false; - } - else - { - areAllPeersLocallyOnHold = true; - for (CallPeer peer : callConference.getCallPeers()) - { - CallPeerState state = peer.getState(); - - // If we have clicked the hold button in a full screen mode - // we need to update the state of the call dialog hold button. - if (!state.equals(CallPeerState.ON_HOLD_LOCALLY) - && !state.equals(CallPeerState.ON_HOLD_MUTUALLY)) - { - areAllPeersLocallyOnHold = false; - break; - } - } - } - - // If we have clicked the hold button in a full screen mode or selected - // hold of the peer menu in a conference call we need to update the - // state of the call dialog hold button. - holdButton.setSelected(areAllPeersLocallyOnHold); - } - - /** - * Updates the visible state/property of {@link #mergeButton} if - * the merge button is present. - */ - private void updateMergeButtonState() - { - // If the merge button isn't present, for example if it's hidden by - // its configuration property we have nothing more to do here. - if (mergeButton == null) - return; - - List conferences = new ArrayList(); - int cpt = 0; - - for (Call call : CallManager.getInProgressCalls()) - { - CallConference conference = call.getConference(); - - if (conference == null) - cpt++; - else if (!conferences.contains(conference)) - { - conferences.add(conference); - cpt++; - } - else - continue; - - if (cpt > 1) - break; - } - - mergeButton.setVisible(cpt > 1); - } - - /** - * Updates {@link #settingsPanel} from the model of this view. The update is - * performed in the AWT event dispatching thread. - *

- * The center of this view is occupied by {@link #callPanel}, the bottom of - * this view is dedicated to settingsPanel. The method - * {@link #updateViewFromModelInEventDispatchThread()} updates - * callPanel from the model of this view and then invokes the - * method updateSettingsPanelInEventDispatchThread(). Thus this - * whole view is updated so that it depicts the current state of its model. - *

- * - * @param callConferenceIsEnded true if the method - * updateViewFromModelInEventDispatchThread() considers the - * {@link #callConference} ended; otherwise, false. When the - * callConference is considered ended, the callPanel - * instance will not be switched to a specific type (one-to-one, audio-only, - * or audio/video) because, otherwise, the switch will leave it - * null and this view will remain blank. In such a case, - * settingsPanel may wish to do pretty much the same but disable - * and/or hide the buttons it contains. - */ - private void updateSettingsPanelInEventDispatchThread( - boolean callConferenceIsEnded) - { - /* - * XXX The method directly delegates to the method - * doUpdateSettingsPanelInEventDispatchThread at the time of this - * writing which may be considered a waste. But in the fashion of the - * method updateViewFromModelInEventDispatchThread we have made it easy - * to add code before and/or after the invocation of the delegate. - */ - doUpdateSettingsPanelInEventDispatchThread(callConferenceIsEnded); - } - - /** - * Updates this view i.e. CallPanel so that it depicts the current - * state of its model i.e. callConference. - */ - 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. - */ - if (!disposed) - { - if (SwingUtilities.isEventDispatchThread()) - updateViewFromModelInEventDispatchThread(); - else - { - SwingUtilities.invokeLater( - updateViewFromModelInEventDispatchThread); - } - } - } - - /** - * Updates this view i.e. CallPanel so that it depicts the current - * state of its model i.e. callConference. 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. - */ - if (disposed) - return; - - /* - * We may add, remove, show, and hide various Components of the user - * interface hierarchy of this instance bellow. Consequently, this view - * may become larger in width and/or height than its current Frame has - * dedicated to it. Try to detect such cases and attempt to adjust the - * Frame's size accordingly. - */ - Dimension oldPrefSize = getPreferredSize(); - - doUpdateViewFromModelInEventDispatchThread(); - - /* - * We may have added, removed, shown, and hidden various Components of - * the user interface hierarchy of this instance above. Consequently, - * this view may have become larger in width and/or height than its - * current Frame has dedicated to it. Try to detect such cases and - * attempt to adjust the Frame's size accordingly. - */ - Dimension newPrefSize = getPreferredSize(); - - if ((newPrefSize != null) - && ((newPrefSize.height > getHeight()) - || (newPrefSize.width > getWidth()))) - { - int oldPrefHeight, oldPrefWidth; - - if (oldPrefSize == null) - { - oldPrefHeight = 0; - oldPrefWidth = 0; - } - else - { - oldPrefHeight = oldPrefSize.height; - oldPrefWidth = oldPrefSize.width; - } - if ((newPrefSize.height != oldPrefHeight) - || (newPrefSize.width != oldPrefWidth)) - { - ensureSize( - this, - newPrefSize.width, newPrefSize.height); - } - } - } - - /** - * Listens for contact status changes and updates the image of the - * chat message button. - * @param evt the ContactPresenceStatusChangeEvent describing the status - */ - @Override - public void contactPresenceStatusChanged(ContactPresenceStatusChangeEvent evt) - { - Contact contact = getIMCapableCallPeers(1).get(0); - - if(contact != null && contact.equals(evt.getSourceContact())) - { - chatButton.setIconImage( - Constants.getMessageStatusIcon(contact.getPresenceStatus())); - chatButton.repaint(); - } - } - - /** - * Implements the listener which listens to events fired by the - * CallConference depicted by this instance, the Calls - * participating in that telephony conference, the CallPeers - * associated with those Calls and the ConferenceMembers - * participating in the telephony conferences organized by those - * CallPeers. Updates this view i.e. CallPanel so that it depicts - * the current state of its model i.e. {@link #callConference}. - */ - private class CallConferenceListener - extends CallPeerConferenceAdapter - implements CallChangeListener, - PropertyChangeListener - { - /** - * {@inheritDoc} - * - * Invokes {@link #onCallPeerEvent(CallPeerEvent)} because the - * CallPeerEvent allows distinguishing whether a - * CallPeer was added or removed by examining its - * eventID. - */ - public void callPeerAdded(CallPeerEvent ev) - { - onCallPeerEvent(ev); - } - - /** - * {@inheritDoc} - * - * Invokes {@link #onCallPeerEvent(CallPeerEvent)} because the - * CallPeerEvent allows distinguishing whether a - * CallPeer was added or removed by examining its - * eventID. - */ - public void callPeerRemoved(CallPeerEvent ev) - { - onCallPeerEvent(ev); - } - - /** - * {@inheritDoc} - * - * Invokes {@link #onEventObject(EventObject)}. - */ - public void callStateChanged(CallChangeEvent ev) - { - onEventObject(ev); - } - - /** - * {@inheritDoc} - * - * Invokes {@link #onEventObject(EventObject)}. - */ - @Override - protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev) - { - onEventObject(ev); - } - - /** - * Notifies this CallChangeListener about a specific - * CallPeerEvent i.e. that a CallPeer was added to or - * removed from a Call. Invokes - * {@link #onEventObject(EventObject)}. - * - * @param ev the CallPeerEvent to notify this - * CallChangeListener about i.e. which specifies the - * CallPeer which was added/removed and the Call - * to/from which it was added/removed - */ - private void onCallPeerEvent(CallPeerEvent ev) - { - onEventObject(ev); - } - - /** - * Invoked by the various listener method implementations provided by - * this CallConferenceListener to notify this instance about an - * EventObject related to the CallConference depicted - * by this CallPanel, the Calls participating in it, - * the CallPeers associated with them, the - * ConferenceMembers participating in any telephony conferences - * organized by them, etc. In other words, notifies this instance about - * any change which may cause an update to be required so that this view - * i.e. CallPanel depicts the current state of its model i.e. - * {@link CallPanel#callConference}. - * - * @param ev the EventObject this instance is being notified - * about. - */ - private void onEventObject(EventObject ev) - { - onCallConferenceEventObject(ev); - } - - /** - * {@inheritDoc} - * - * Invokes {@link #onEventObject(EventObject)}. - */ - public void propertyChange(PropertyChangeEvent ev) - { - String propertyName = ev.getPropertyName(); - - /* - * If a Call is added to or removed from the CallConference depicted - * by this CallPanel, an update of the view from its model will most - * likely be required. - */ - if (propertyName.equals(CallConference.CALLS)) - { - onEventObject(ev); - } - else if (propertyName.equals(CallContainer.PROP_FULL_SCREEN)) - { - if (ev.getSource().equals(callWindow.getFrame())) - { - try - { - /* - * We'll turn the switching between full-screen and - * windowed mode into a model state because a - * significant part of this view changes upon such a - * switch. - */ - onEventObject(ev); - } - finally - { - callWindowPropertyChange(ev); - } - } - } - } - } -} +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.gui.main.call; + +import java.awt.*; +import java.awt.Container; +import java.awt.event.*; +import java.beans.*; +import java.io.*; +import java.util.*; +import java.util.List; + +import javax.swing.*; +import javax.swing.Timer; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.event.*; +import net.java.sip.communicator.impl.gui.main.call.conference.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.impl.gui.utils.Constants; +import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.gui.call.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.media.*; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.Logger; +import net.java.sip.communicator.util.skin.*; + +import org.jitsi.service.neomedia.*; +import org.jitsi.util.*; +import org.jitsi.util.event.*; +import org.osgi.framework.*; + +/** + * The dialog created for a given call. + * + * Ordered buttons we are adding/removing, numbers are the index we have set. + * And the order that will be kept. + * 0 dialButton + * 1 conferenceButton + * 2 holdButton + * 3 recordButton + * 4 mergeButton + * 5 transferCallButton + * 6 localLevel + * 7 remoteLevel + * 8 desktopSharingButton + * 9 resizeVideoButton + * 10 fullScreenButton + * 11 videoButton + * 12 showHideVideoButton + * 19 chatButton + * 20 infoButton + * + * @author Yana Stamcheva + * @author Adam Netocny + * @author Lyubomir Marinov + * @author Hristo Terezov + */ +public class CallPanel + extends TransparentPanel + implements ActionListener, + PluginComponentListener, + Skinnable, + ConferencePeerViewListener, + ContactPresenceStatusListener +{ + /** + * The chat button name. + */ + private static final String CHAT_BUTTON = "CHAT_BUTTON"; + + /** + * The conference button name. + */ + private static final String CONFERENCE_BUTTON = "CONFERENCE_BUTTON"; + + /** + * The dial button name. + */ + private static final String DIAL_BUTTON = "DIAL_BUTTON"; + + /** + * The info button name. + */ + private static final String INFO_BUTTON = "INFO_BUTTON"; + + /** + * The info button name. + */ + private static final String CRM_BUTTON = "CRM_BUTTON"; + + /** + * The logger for this class. + */ + private static final Logger logger = Logger.getLogger(CallDialog.class); + + /** + * The hang up button name. + */ + private static final String MERGE_BUTTON = "MERGE_BUTTON"; + + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * Property to disable the info button. + */ + private static final String HIDE_CALL_INFO_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_INFO_BUTTON"; + + /** + * Property to enable the CRM button. + */ + private static final String SHOW_CRM_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.SHOW_CRM_BUTTON"; + + /** + * Property to disable the conference "add to call" button. + */ + private static final String HIDE_CONFERENCE_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_CONFERENCE_BUTTON"; + + /** + * Property to disable the record button. + */ + private static final String HIDE_CALL_RECORD_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_RECORD_BUTTON"; + + /** + * Property to disable the "call merge" button. + */ + private static final String HIDE_CALL_MERGE_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_MERGE_BUTTON"; + + /** + * Property to disable the "call merge" button. + */ + private static final String HIDE_CALL_TRANSFER_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_TRANSFER_BUTTON"; + + /** + * Property to disable the "hold" button. + */ + private static final String HIDE_CALL_HOLD_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_CALL_HOLD_BUTTON"; + + /** + * Property to disable the dial button. + */ + private static final String HIDE_DIAL_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_DIAL_BUTTON"; + + /** + * Property to disable the video button. + */ + private static final String HIDE_VIDEO_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_VIDEO_BUTTON"; + + /** + * Property to disable the button, which shows/hides participants in video + * conferences. + */ + private static final String HIDE_PEERS_LIST_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_PEERS_LIST_BUTTON"; + + /** + * Indicates if the participants list in a video conference is visible by + * default. + */ + private static final String PEERS_LIST_HIDDEN_PROP + = "net.java.sip.communicator.impl.gui.main.call.PEERS_LIST_HIDDEN"; + + /** + * Property to disable the desktop sharing button. + */ + private static final String HIDE_DESKTOP_SHARING_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_DESKTOP_SHARING_BUTTON"; + + /** + * Property to disable the full screen button. + */ + private static final String HIDE_FULL_SCREEN_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_FULL_SCREEN_BUTTON"; + + /** + * Property to disable the "show/hide local video" button. + */ + private static final String HIDE_TOGGLE_VIDEO_BUTON_PROP + = "net.java.sip.communicator.impl.gui.main.call.HIDE_TOGGLE_VIDEO_BUTTON"; + + /** + * The Component which is at the bottom of this view and contains + * {@link #settingsPanel}. It overrides the Swing-defined background on OS + * X so it needs explicit updating upon switching between full-screen and + * windowed mode in order to respect any background-related settings of the + * ancestors such as black background in full-screen mode. + */ + private JComponent bottomBar; + + /** + * The {@link CallConference} instance depicted by this CallPanel. + */ + private final CallConference callConference; + + /** + * The listener which listens to events fired by the CallConference + * depicted by this instance, the Calls participating in that + * telephony conference, the CallPeers associated with those + * Calls and the ConferenceMembers participating in the + * telephony conferences organized by those CallPeers. Updates this + * view i.e. CallPanel so that it depicts the current state of its model + * i.e. {@link #callConference}. + */ + private final CallConferenceListener callConferenceListener + = new CallConferenceListener(); + + /** + * The time in milliseconds at which the telephony call/conference depicted + * by this CallPanel (i.e. {@link #callConference}) has started. + */ + private long callConferenceStartTime; + + /** + * A timer to count call duration. + */ + private Timer callDurationTimer; + + /** + * The Frame used to display this call information statistics. + */ + private CallInfoFrame callInfoFrame; + + /** + * The panel representing the call. For conference calls this would be an + * instance of ConferenceCallPanel and for one-to-one calls this + * would be an instance of OneToOneCallPanel. + */ + private JComponent callPanel; + + /** + * Parent window. + */ + private final CallContainer callWindow; + + /** + * Chat button. + */ + private SIPCommButton chatButton; + + /** + * The operation set that will be used to update chatButton icon and + * the corresponding contact. + */ + private OperationSetPresence operationSetPresence; + + /** + * The conference button. + */ + private CallToolBarButton conferenceButton; + + /** + * The desktop sharing button. + */ + private DesktopSharingButton desktopSharingButton; + + /** + * The dial button, which opens a keypad dialog. + */ + private CallToolBarButton dialButton; + + /** + * The dial pad dialog opened when the dial button is clicked. + */ + private DialpadDialog dialpadDialog; + + /** + * The indicator which determines whether {@link #dispose()} has already + * been invoked on this instance. If true, this instance is + * considered non-functional and is to be left to the garbage collector. + */ + private boolean disposed = false; + + /** + * The handler for DTMF tones. + */ + private DTMFHandler dtmfHandler; + + /** + * The full screen button. + */ + private FullScreenButton fullScreenButton; + + /** + * HangUp button. + */ + private SIPCommButton hangupButton; + + /** + * The hold button. + */ + private HoldButton holdButton; + + /** + * Info button. + */ + private SIPCommButton infoButton; + + /** + * CRM button. + */ + private CallToolBarButton crmButton; + + /** + * Indicates if the call timer has been started. + */ + private boolean isCallTimerStarted = false; + + /** + * Sound local level label. + */ + private InputVolumeControlButton localLevel; + + /** + * Merge button. + */ + private CallToolBarButton mergeButton; + + /** + * The button which allows starting and stopping the recording of + * {@link #callConference}. + */ + private RecordButton recordButton; + + /** + * Sound remote level label. + */ + private Component remoteLevel; + + /** + * The video resize button. + */ + private ResizeVideoButton resizeVideoButton; + + /** + * The panel containing call settings. + */ + private CallToolBar settingsPanel; + + /** + * The button responsible for hiding/showing the local video. + */ + private ShowHideVideoButton showHideVideoButton; + + /** + * The button, which shows / hides the participants list in a video + * conference. + */ + private ShowHidePeersButton showHidePeersButton; + + /** + * The title of this call container. + */ + private String title; + + /** + * A collection of listeners, registered for call title change events. + */ + private Collection titleListeners + = new Vector(); + + /** + * The transfer call button. + */ + private TransferCallButton transferCallButton; + + /** + * The facility which aids this instance in the dealing with the + * video-related information. + */ + private final UIVideoHandler2 uiVideoHandler; + + /** + * Indicates if this call panel should be closed immediately after hang up + * or should wait some time so that the user can be notified of the last + * state. By default we wait, so that the user can be notified of the + * current state of the call. + */ + private boolean isCloseWaitAfterHangup = true; + + /** + * The Observer which listens to {@link #uiVideoHandler} about + * changes in the video-related information. + */ + private final Observer uiVideoHandlerObserver + = new Observer() + { + public void update(Observable o, Object arg) + { + uiVideoHandlerUpdate(arg); + } + }; + + /** + * The Runnable 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(); + } + }; + + /** + * The video button. + */ + private LocalVideoButton videoButton; + + /** + * Initializes a new CallPanel which is to depict a specific + * CallConference. + * + * @param callConference the CallConference to be depicted by the + * new instance + * @param callWindow the parent window in which the new instance will be + * added + */ + public CallPanel( CallConference callConference, + CallContainer callWindow) + { + super(new BorderLayout()); + + this.callConference = callConference; + this.callWindow = callWindow; + + uiVideoHandler = new UIVideoHandler2(this.callConference); + + callDurationTimer + = new Timer( + 1000, + new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + setCallTitle(callConferenceStartTime); + } + }); + callDurationTimer.setRepeats(true); + + // The call duration parameter is not known yet. + setCallTitle(0); + + initializeUserInterfaceHierarchy(); + + dtmfHandler = new DTMFHandler(this); + + /* + * Adds the listeners which will observe the model and will trigger the + * updates of this view from it. + */ + this.callConference.addCallChangeListener(callConferenceListener); + this.callConference.addCallPeerConferenceListener( + callConferenceListener); + this.callConference.addPropertyChangeListener(callConferenceListener); + uiVideoHandler.addObserver(uiVideoHandlerObserver); + + callWindow.getFrame().addPropertyChangeListener( + CallContainer.PROP_FULL_SCREEN, + callConferenceListener); + + updateViewFromModel(); + + initPluginComponents(); + } + + /** + * Handles action events. + * @param evt the ActionEvent that was triggered + */ + public void actionPerformed(ActionEvent evt) + { + JButton button = (JButton) evt.getSource(); + String buttonName = button.getName(); + + if (buttonName.equals(MERGE_BUTTON)) + { + CallManager.mergeExistingCalls( + callConference, + CallManager.getInProgressCalls()); + } + else if (buttonName.equals(DIAL_BUTTON)) + { + if (dialpadDialog == null) + dialpadDialog = this.getDialpadDialog(); + + if(!dialpadDialog.isVisible()) + { + dialpadDialog.pack(); + + Point location = new Point( button.getX(), + button.getY() + button.getHeight()); + SwingUtilities.convertPointToScreen( + location, button.getParent()); + + dialpadDialog.setLocation( + (int) location.getX() + 2, + (int) location.getY() + 2); + + dialpadDialog.addWindowFocusListener(dialpadDialog); + dialpadDialog.setVisible(true); + } + else + { + dialpadDialog.removeWindowFocusListener(dialpadDialog); + dialpadDialog.setVisible(false); + } + } + else if (buttonName.equals(CONFERENCE_BUTTON)) + { + ConferenceInviteDialog inviteDialog; + + if (callConference.isJitsiVideobridge()) + { + inviteDialog + = new ConferenceInviteDialog( + callConference, + callConference.getCalls().get(0) + .getProtocolProvider(), + true); + } + else + inviteDialog = new ConferenceInviteDialog(callConference); + + inviteDialog.setVisible(true); + } + else if (buttonName.equals(CHAT_BUTTON)) + { + /* + * If there is exactly 1 CallPeer capable of instant messaging, then + * we'll try to start a chat with her. + */ + /* + * TODO The following is very likely to block the user interface in + * a noticeable way sooner or later. + */ + List imCapableCallPeers = getIMCapableCallPeers(1); + + if (imCapableCallPeers.size() == 1) + { + Contact contact = imCapableCallPeers.get(0); + MetaContact metaContact + = GuiActivator.getContactListService() + .findMetaContactByContact(contact); + GuiActivator.getUIService().getChatWindowManager().startChat( + metaContact); + } + } + else if (buttonName.equals(INFO_BUTTON)) + { + if (callInfoFrame == null) + { + callInfoFrame = new CallInfoFrame(callConference); + addCallTitleListener(callInfoFrame); + } + callInfoFrame.setVisible( + callInfoFrame.hasCallInfo() && !callInfoFrame.isVisible()); + } + else if (buttonName.equals(CRM_BUTTON)) + { + String command = + GuiActivator.getConfigurationService().getString( + "net.java.sip.communicator.impl.gui.main.call.CRM_COMMAND"); + if (command == null) + { + return; + } + + List callPeers = callConference.getCallPeers(); + if (callPeers.isEmpty()) + { + logger.info("No CallPeer for CRM application found."); + return; + } + + command = String.format(command, callPeers.get(0).getAddress()); + try + { + logger.info("Launching CRM application: " + command); + Runtime.getRuntime().exec(command); + } + catch (IOException e) + { + logger.error("Unable launch CRM application", e); + } + } + } + + /** + * Executes the action associated with the "Hang up" button which may be + * invoked by clicking the button in question or by closing this dialog. + * + * @param closeWait true to close this instance with a few seconds + * of delay or false to close it immediately + */ + public void actionPerformedOnHangupButton(boolean closeWait) + { + isCloseWaitAfterHangup = closeWait; + + this.disposeCallInfoFrame(); + + /* + * It is the responsibility of CallManager to close this CallPanel + * when a Call is ended. + */ + if (callConference.getCallCount() > 0) + CallManager.hangupCalls(callConference); + /* + * If however there are no more calls related to this panel we will + * close the window directly. This could happen in the case, where + * the other side has already hanged up the call, the call window shows + * the state disconnected and we press the hang up button. In this + * case the contained call is already null and we should only close the + * call window. + */ + else + callWindow.close(this, false); + } + + /** + * Indicates if this call panel should be closed immediately after hang up + * or should wait some time so that the user can be notified of the last + * state. + * + * @return true to indicate that when hanged up this call panel + * should not be closed immediately, false - otherwise + */ + public boolean isCloseWaitAfterHangup() + { + return isCloseWaitAfterHangup; + } + + /** + * Adds the given CallTitleListener to the list of listeners, + * notified for call title changes. + * + * @param l the CallTitleListener to add + */ + public void addCallTitleListener(CallTitleListener l) + { + synchronized (titleListeners) + { + titleListeners.add(l); + } + } + + /** + * Adds remote video specific components. + * + * @param callPeer the CallPeer + */ + public void addRemoteVideoSpecificComponents(CallPeer callPeer) + { + if(CallManager.isVideoQualityPresetSupported(callPeer)) + { + if(resizeVideoButton == null) + { + resizeVideoButton = new ResizeVideoButton(callPeer.getCall()); + resizeVideoButton.setIndex(9); + } + + if(resizeVideoButton.countAvailableOptions() > 1) + { + settingsPanel.add(resizeVideoButton); + settingsPanel.revalidate(); + settingsPanel.repaint(); + } + } + } + + /** + * Notifies this instance about a PropertyChangeEvent fired by + * {@link #callWindow}. + * + * @param ev the PropertyChangeEvent fired by callWindow + * to notify this instance about + */ + private void callWindowPropertyChange(PropertyChangeEvent ev) + { + /* + * We are registered for CallContainer#PROP_FULL_SCREEN only. This + * instance will fire the notification as its own to allow listeners to + * register with a source which is more similar to them with respect to + * life span. + */ + try + { + if (OSUtils.IS_MAC && (bottomBar != null)) + bottomBar.setOpaque(!isFullScreen()); + } + finally + { + firePropertyChange( + ev.getPropertyName(), + ev.getOldValue(), ev.getNewValue()); + } + } + + /** + * Count the number of the buttons in the supplied components. + * @param cs the components to search for buttons. + * @return number of buttons. + */ + private int countButtons(Component[] cs) + { + int count = 0; + + for(Component c : cs) + { + if(c instanceof SIPCommButton || c instanceof SIPCommToggleButton) + count++; + if(c instanceof Container) + count += countButtons(((Container)c).getComponents()); + } + + return count; + } + + /** + * Creates the bottom bar panel for this CallPanel. + * + * @return a new bottom bar panel for this CallPanel + */ + private JComponent createBottomBar() + { + bottomBar + = new TransparentPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); + bottomBar.setBorder(BorderFactory.createEmptyBorder(0, 30, 2, 30)); + + /* + * The bottomBar on OS X overrides the Swing-defined background. + * However, full-screen display usually uses a black background. The + * black background will be set elsewhere on an ancestor but we have to + * make sure that bottomBar's background does not interfere with the + * setting. + */ + if (OSUtils.IS_MAC) + { + bottomBar.setOpaque(!isFullScreen()); + bottomBar.setBackground( + new Color(GuiActivator.getResources().getColor( + "service.gui.MAC_PANEL_BACKGROUND"))); + } + + bottomBar.add(settingsPanel); + + return bottomBar; + } + + /** + * Releases the resources acquired by this instance which require explicit + * disposal (e.g. any listeners added to the depicted + * CallConference, the participating Calls, and their + * associated CallPeers). Invoked by CallManager when it + * determines that this CallPanel is no longer necessary. + */ + void dispose() + { + disposed = true; + + callConference.removeCallChangeListener(callConferenceListener); + callConference.removeCallPeerConferenceListener(callConferenceListener); + callConference.removePropertyChangeListener(callConferenceListener); + + uiVideoHandler.deleteObserver(uiVideoHandlerObserver); + uiVideoHandler.dispose(); + + callWindow.getFrame().removePropertyChangeListener( + CallContainer.PROP_FULL_SCREEN, + callConferenceListener); + + if (callPanel != null) + { + if(callPanel instanceof BasicConferenceCallPanel) + { + ((BasicConferenceCallPanel) callPanel) + .removePeerViewListener(this); + } + ((CallRenderer) callPanel).dispose(); + } + + // clears the contact status listener + if(operationSetPresence != null) + { + operationSetPresence.removeContactPresenceStatusListener(this); + } + } + + /** + * Disposes the call info frame if it exists. + */ + public void disposeCallInfoFrame() + { + if (callInfoFrame != null) + callInfoFrame.dispose(); + } + + /** + * Updates {@link #settingsPanel} from the model of this view. The update is + * performed in the AWT event dispatching thread. + *

+ * The center of this view is occupied by {@link #callPanel}, the bottom of + * this view is dedicated to settingsPanel. The method + * {@link #updateViewFromModelInEventDispatchThread()} updates + * callPanel from the model of this view and then invokes the + * method updateSettingsPanelInEventDispatchThread(). Thus this + * whole view is updated so that it depicts the current state of its model. + *

+ * + * @param callConferenceIsEnded true if the method + * updateViewFromModelInEventDispatchThread() considers the + * {@link #callConference} ended; otherwise, false. When the + * callConference is considered ended, the callPanel + * instance will not be switched to a specific type (one-to-one, audio-only, + * or audio/video) because, otherwise, the switch will leave it + * null and this view will remain blank. In such a case, + * settingsPanel may wish to do pretty much the same but disable + * and/or hide the buttons it contains. + */ + private void doUpdateSettingsPanelInEventDispatchThread( + boolean callConferenceIsEnded) + { + settingsPanel.setFullScreen(isFullScreen()); + + boolean isConference = (callPanel instanceof BasicConferenceCallPanel); + + /* + * For whatever reason, we're treating the localLevel and the + * remoteLevel buttons differently and we're adding and removing them in + * accord with the conference state of the user interface. + */ + if (isConference) + { + settingsPanel.add(localLevel); + settingsPanel.add(remoteLevel); + } + else + { + settingsPanel.remove(localLevel); + settingsPanel.remove(remoteLevel); + } + + /* + * We do not support chat conferencing with the participants in a + * telephony conference at this time so we do not want the chatButton + * visible in such a scenario. + */ + List imContacts = getIMCapableCallPeers(1); + chatButton.setVisible( + !isConference && (imContacts.size() == 1)); + if(chatButton.isVisible() && operationSetPresence == null) + { + Contact contact = imContacts.get(0); + operationSetPresence = + contact.getProtocolProvider() + .getOperationSet(OperationSetPresence.class); + if(operationSetPresence != null) + operationSetPresence.addContactPresenceStatusListener(this); + + chatButton.setIconImage( + Constants.getMessageStatusIcon(contact.getPresenceStatus())); + chatButton.repaint(); + } + + updateHoldButtonState(); + updateMergeButtonState(); + + List calls = callConference.getCalls(); + /* + * OperationSetAdvancedTelephony implements call transfer. The feature + * is not supported if the local user/peer is a conference focus. + * Instead of disabling the transferCallButton in this case though, we + * want it hidden. + */ + boolean advancedTelephony = !calls.isEmpty(); + boolean telephonyConferencing = false; + boolean videoTelephony = false; + boolean videoTelephonyIsLocalVideoAllowed = false; + boolean videoTelephonyIsLocalVideoStreaming = false; + boolean desktopSharing = false; + boolean desktopSharingIsStreamed = false; + boolean allCallsConnected = true; + + for (Call call : calls) + { + ProtocolProviderService pps = call.getProtocolProvider(); + + /* + * The transferCallButton requires OperationSetAdvancedTelephony + * for all Calls. + */ + if (advancedTelephony) + { + OperationSetAdvancedTelephony osat + = pps.getOperationSet(OperationSetAdvancedTelephony.class); + + if (osat == null) + advancedTelephony = false; + } + + /* + * The conferenceButton needs at least one Call with + * OperationSetTelephonyConferencing, + */ + if (!telephonyConferencing) + { + OperationSetTelephonyConferencing ostc + = pps.getOperationSet( + OperationSetTelephonyConferencing.class); + + if (ostc != null) + telephonyConferencing = true; + } + + if (!videoTelephony + || !videoTelephonyIsLocalVideoAllowed + || !videoTelephonyIsLocalVideoStreaming) + { + OperationSetVideoTelephony osvt + = pps.getOperationSet(OperationSetVideoTelephony.class); + + if (osvt != null) + { + if (!videoTelephony) + videoTelephony = true; + if (!videoTelephonyIsLocalVideoAllowed + && osvt.isLocalVideoAllowed(call)) + videoTelephonyIsLocalVideoAllowed = true; + if (!videoTelephonyIsLocalVideoStreaming + && osvt.isLocalVideoStreaming(call)) + videoTelephonyIsLocalVideoStreaming = true; + } + } + + if(!desktopSharing) + { + OperationSetDesktopStreaming osds + = pps.getOperationSet( + OperationSetDesktopStreaming.class); + if(osds != null) + { + desktopSharing = true; + + if(videoTelephonyIsLocalVideoStreaming + && call instanceof MediaAwareCall + && ((MediaAwareCall) call).getMediaUseCase() + == MediaUseCase.DESKTOP) + { + desktopSharingIsStreamed = true; + } + } + } + + if (CallState.CALL_IN_PROGRESS != call.getCallState()) + { + allCallsConnected = false; + } + } + + if(conferenceButton != null) + conferenceButton.setEnabled(telephonyConferencing); + + if(transferCallButton != null) + { + transferCallButton.setEnabled(advancedTelephony); + transferCallButton.setVisible(!callConference.isConferenceFocus()); + } + + /* + * The videoButton is a beast of its own kind because it depends not + * only on the state of the depicted telephony conference but also on + * the global application state. + */ + if(videoButton != null) + { + videoButton.setEnabled(allCallsConnected && videoTelephony); + videoButton.setSelected(videoTelephonyIsLocalVideoAllowed); + + /* + * Consequently, the showHideVideoButton which depends on videoButton + * has to be updated depending on the state of the videoButton as well. + */ + if(showHideVideoButton != null) + { + showHideVideoButton.setEnabled( + videoButton.isEnabled() + && videoTelephonyIsLocalVideoAllowed); + showHideVideoButton.setSelected( + showHideVideoButton.isEnabled() + && uiVideoHandler.isLocalVideoVisible()); + showHideVideoButton.setVisible(showHideVideoButton.isEnabled()); + } + } + + if (showHidePeersButton != null) + { + showHidePeersButton.setVisible(isConference + && CallManager.isVideoStreaming(callConference)); + } + + // The desktop sharing button depends on the operation set desktop + // sharing server. + if(desktopSharingButton != null) + { + desktopSharingButton.setEnabled(desktopSharing); + desktopSharingButton.setSelected(desktopSharingIsStreamed); + } + + if (callPanel instanceof OneToOneCallPanel) + { + OneToOneCallPanel oneToOneCallPanel = (OneToOneCallPanel) callPanel; + if(desktopSharingIsStreamed) + oneToOneCallPanel.addDesktopSharingComponents(); + else + oneToOneCallPanel.removeDesktopSharingComponents(); + } + } + + /** + * Updates this view i.e. CallPanel so that it depicts the current + * state of its model i.e. callConference. The update is performed + * in the AWT event dispatching thread. + */ + private void doUpdateViewFromModelInEventDispatchThread() + { + /* + * If the telephony conference depicted by this instance has ended, do + * not update the user interface because it will be left blank. It is + * CallManager's responsibility to dispose of this CallPanel after its + * telephony conference has ended. Additionally, the various types of + * callPanel will usually require at least one CallPeer in order to not + * be blank. The absence of CallPeers usually indicates that a Call and, + * respectively, a telephony conference has ended. So it makes some + * sense to skip the update in such cases in order to try to not have + * the user interface blank. + */ + if (callConference.isEnded() + || (callConference.getCallPeerCount() == 0)) + { + /* + * However, the settingsPanel contains buttons which may still need + * to be disabled and/or hidden. + */ + updateSettingsPanelInEventDispatchThread(true); + return; + } + + boolean isConference = isConference(); + boolean isVideo = CallManager.isVideoStreaming(callConference); + CallPeer callPeer = null; + boolean validateAndRepaint = false; + + if (callPanel != null) + { + boolean removeCallPanel; + + if (isConference) + { + if (callPanel instanceof BasicConferenceCallPanel) + { + if (isVideo) + { + removeCallPanel + = !(callPanel instanceof VideoConferenceCallPanel); + } + else + { + removeCallPanel + = (callPanel instanceof VideoConferenceCallPanel); + } + } + else + { + removeCallPanel = true; + } + } + else + { + if (callPanel instanceof OneToOneCallPanel) + { + if (callPeer == null) + { + List callPeers + = callConference.getCallPeers(); + + if (!callPeers.isEmpty()) + callPeer = callPeers.get(0); + } + removeCallPanel + = !((OneToOneCallPanel) callPanel).getCallPeer().equals( + callPeer); + } + else + { + if( (callPanel instanceof BasicConferenceCallPanel) && + ((BasicConferenceCallPanel) callPanel) + .hasDelayedCallPeers()) + { + removeCallPanel = false; + } + else + { + removeCallPanel = true; + } + + } + } + if (removeCallPanel) + { + remove(callPanel); + validateAndRepaint = true; + try + { + ((CallRenderer) callPanel).dispose(); + } + finally + { + callPanel = null; + } + } + } + if (callPanel == null) + { + if (isConference) + { + if (isVideo) + { + callPanel + = new VideoConferenceCallPanel( + this, + callConference, + uiVideoHandler); + } + else + { + callPanel + = new AudioConferenceCallPanel(this, callConference); + } + + ((BasicConferenceCallPanel) callPanel) + .addPeerViewlListener(this); + } + else + { + if (callPeer == null) + { + List callPeers = callConference.getCallPeers(); + + if (!callPeers.isEmpty()) + callPeer = callPeers.get(0); + } + if (callPeer != null) + { + callPanel + = new OneToOneCallPanel(this, callPeer, uiVideoHandler); + } + } + if (callPanel != null) + { + add(callPanel, BorderLayout.CENTER); + validateAndRepaint = true; + } + } + + try + { + /* + * The center of this view is occupied by callPanel and we have just + * updated it. The bottom of this view is dedicated to settingsPanel + * so we have to update it as well. + */ + updateSettingsPanelInEventDispatchThread(false); + } + finally + { + /* + * It seems that AWT/Swing does not validate and/or repaint this + * Container (enough) and, consequently, its display may not update + * itself with an up-to-date drawing of the current callPanel. + */ + if (validateAndRepaint) + { + if (isDisplayable()) + { + validate(); + repaint(); + } + else + doLayout(); + } + } + } + + /** + * Attempts to give a specific Component a visible rectangle with a + * specific width and a specific height if possible and sane by resizing + * the Window which contains this instance. + * + * @param component the Component which requests a visible + * rectangle with the specified width and height + * @param width the width of the visible rectangle requested by the + * specified component + * @param height the height of the visible rectangle requested by the + * specified component + */ + private void ensureSize(Component component, int width, int height) + { + CallContainer callContainer = getCallWindow(); + + if (callContainer != null) + callContainer.ensureSize(component, width, height); + } + + /** + * Notifies interested listeners of a call title change. + */ + private void fireTitleChangeEvent() + { + Iterator listeners; + + synchronized (titleListeners) + { + listeners = new Vector(titleListeners).iterator(); + } + + while (listeners.hasNext()) + { + listeners.next().callTitleChanged(this); + } + } + + /** + * Returns the CallConference depicted by this CallPanel + * + * @return the CallConference depicted by this + * CallConference + */ + public CallConference getCallConference() + { + return callConference; + } + + /** + * Returns the initial call title. The call title could be then changed by + * call setCallTitle. + * + * @return the call title + */ + public String getCallTitle() + { + return title; + } + + /** + * Returns the parent call window. + * + * @return the parent call window + */ + public CallContainer getCallWindow() + { + return callWindow; + } + + /** + * Returns the currently used CallRenderer. + * @return the currently used CallRenderer + */ + public CallRenderer getCurrentCallRenderer() + { + return (CallRenderer) callPanel; + } + + /** + * Returns the DialpadDialog corresponding to this CallDialog. + * + * @return the DialpadDialog corresponding to this CallDialog. + */ + private DialpadDialog getDialpadDialog() + { + return new DialpadDialog(dtmfHandler); + } + + /** + * Finds the Contacts which are participating in the telephony + * conference depicted by this instance and which are capable of instant + * messaging i.e. support {@link OperationSetBasicTelephony}. + * + * @param limit the maximum number of Contacts to be found. Since + * it is expensive in terms of execution time (at very least) to find a + * Contact which stands for a CallPeer (and to query it + * whether it supports instant messaging), it is advised to limit the search + * as much as possible. For example, the chatButton is enabled + * and/or shown only when there is exactly one such Contact so it + * makes perfect sense to specify 1 as the limit in the + * case. + * @return a List of the Contacts which are participating + * in the telephony conference depicted by this instance and which are + * capable of instant messaging i.e. support + * OperationSetBasicTelephony + */ + private List getIMCapableCallPeers(int limit) + { + List callPeers = callConference.getCallPeers(); + List contacts = new ArrayList(callPeers.size()); + + /* + * Choose the CallPeers (or rather their associated Contacts) which are + * capable of basic instant messaging. + */ + for (CallPeer callPeer : callPeers) + { + if (callPeer.getProtocolProvider().getOperationSet( + OperationSetBasicInstantMessaging.class) + != null) + { + /* + * CallPeer#getContact) is more expensive in terms of execution + * than ProtocolProviderService#getOperationSet(Class). + */ + Contact contact = callPeer.getContact(); + + if (contact != null) + contacts.add(contact); + } + else + { + Contact contact = CallManager.getIMCapableCusaxContact(callPeer); + if (contact != null) + { + contacts.add(contact); + } + } + + if (contacts.size() >= limit) + break; + } + return contacts; + } + + /** + * Returns the minimum width needed to show buttons. + * Used to calculate the minimum size of the call dialog. + * @return the minimum width for the buttons. + */ + public int getMinimumButtonWidth() + { + int numberOfButtons = countButtons(settingsPanel.getComponents()); + + if (numberOfButtons > 0) + { + // +1 cause we had and a hangup button + // *32, a button is 28 pixels width and give some border + return (numberOfButtons + 1) * 32; + } + else + return -1; + } + + /** + * Initializes buttons order in the call tool bar. + */ + private void initButtonIndexes() + { + if (dialButton != null) + dialButton.setIndex(0); + if (conferenceButton != null) + conferenceButton.setIndex(1); + if (holdButton != null) + holdButton.setIndex(2); + if (recordButton != null) + recordButton.setIndex(3); + if (mergeButton != null) + mergeButton.setIndex(4); + if (transferCallButton != null) + transferCallButton.setIndex(5); + + localLevel.setIndex(6); + if (remoteLevel instanceof OrderedComponent) + ((OrderedComponent) remoteLevel).setIndex(7); + + if (desktopSharingButton != null) + desktopSharingButton.setIndex(8); + + if (fullScreenButton != null) + fullScreenButton.setIndex(10); + + if (videoButton != null) + videoButton.setIndex(11); + if (showHideVideoButton != null) + showHideVideoButton.setIndex(12); + if (showHidePeersButton != null) + showHidePeersButton.setIndex(13); + + chatButton.setIndex(19); + + if (crmButton != null) + crmButton.setIndex(30); + if (infoButton != null) + infoButton.setIndex(50); + + hangupButton.setIndex(100); + } + + /** + * Initialize plug-in components already registered for this container. + */ + private void initPluginComponents() + { + // Search for plug-in components registered through the OSGI + // BundleContext. + + String osgiFilter + = "(" + + net.java.sip.communicator.service.gui.Container.CONTAINER_ID + + "=" + + net.java.sip.communicator.service.gui.Container + .CONTAINER_CALL_DIALOG.getID() + + ")"; + ServiceReference[] serRefs = null; + + try + { + serRefs + = GuiActivator.bundleContext.getServiceReferences( + PluginComponentFactory.class.getName(), + osgiFilter); + } + catch (InvalidSyntaxException ise) + { + logger.error("Could not obtain plugin reference.", ise); + } + + if (serRefs != null) + { + for (ServiceReference serRef : serRefs) + { + PluginComponentFactory factory + = (PluginComponentFactory) + GuiActivator.bundleContext.getService(serRef); + + PluginComponent component = + factory.getPluginComponentInstance(CallPanel.this); + + component.setCurrentContact( + CallManager.getPeerMetaContact( + callConference.getCallPeers().get(0))); + + settingsPanel.add((Component) component.getComponent()); + } + } + + GuiActivator.getUIService().addPluginComponentListener(this); + } + + /** + * Initializes the user interface hierarchy of this CallPanel i.e. + * the AWT Components which constitute the user interface to be + * displayed by this Component. Their state does not have to depict + * the current state of the model of this view because + * {@link #updateViewFromModel()} will be invoked before this view becomes + * visible. At the center of the user interface of this view is + * {@link #callPanel} but it is dynamically added and removed multiple times + * as part of the execution of the updateViewFromModel method so + * it is not dealt with here. + */ + private void initializeUserInterfaceHierarchy() + { + /* + * The settingsPanel will contain the buttons. It is initialized before + * the buttons in case any of the buttons need it (which is hard to + * determine at the time of this writing). + */ + settingsPanel = new CallToolBar(isFullScreen(), false); + + /* + * TODO CallPanel depicts a whole CallConference which may have multiple + * Calls, new Calls may be added to the CallConference and existing + * Calls may be removed from the CallConference. For example, the + * buttons which accept a Call as an argument should be changed to take + * into account the whole CallConference. + */ + Call aCall = callConference.getCalls().get(0); + + chatButton + = new CallToolBarButton( + ImageLoader.getImage(ImageLoader.CHAT_BUTTON_SMALL_WHITE), + CHAT_BUTTON, + GuiActivator.getResources().getI18NString( + "service.gui.CHAT")); + + if(isButtonEnabled(HIDE_CONFERENCE_BUTON_PROP)) + { + conferenceButton + = new CallToolBarButton( + ImageLoader.getImage(ImageLoader.ADD_TO_CALL_BUTTON), + CONFERENCE_BUTTON, + GuiActivator.getResources().getI18NString( + "service.gui.CREATE_CONFERENCE_CALL")); + } + + if(isButtonEnabled(HIDE_DESKTOP_SHARING_BUTON_PROP)) + { + desktopSharingButton = new DesktopSharingButton(aCall); + } + + if(isButtonEnabled(HIDE_DIAL_BUTON_PROP)) + { + dialButton + = new CallToolBarButton( + ImageLoader.getImage(ImageLoader.DIAL_BUTTON), + DIAL_BUTTON, + GuiActivator.getResources().getI18NString( + "service.gui.DIALPAD")); + } + + if(isButtonEnabled(HIDE_FULL_SCREEN_BUTON_PROP)) + { + fullScreenButton = new FullScreenButton(this); + } + + hangupButton = new HangupButton(this); + + if(isButtonEnabled(HIDE_CALL_HOLD_BUTON_PROP)) + { + holdButton = new HoldButton(aCall); + } + + if(isButtonEnabled(HIDE_CALL_INFO_BUTON_PROP)) + { + infoButton + = new CallToolBarButton( + ImageLoader.getImage(ImageLoader.CALL_INFO), + INFO_BUTTON, + GuiActivator.getResources().getI18NString( + "service.gui.PRESS_FOR_CALL_INFO")); + } + + if(!isButtonEnabled(SHOW_CRM_BUTON_PROP)) + { + crmButton + = new CallToolBarButton( + ImageLoader.getImage(ImageLoader.CRM), + CRM_BUTTON, + GuiActivator.getResources().getI18NString( + "service.gui.PRESS_TO_OPEN_CRM")); + } + + if(isButtonEnabled(HIDE_CALL_MERGE_BUTON_PROP)) + { + mergeButton + = new CallToolBarButton( + ImageLoader.getImage(ImageLoader.MERGE_CALL_BUTTON), + MERGE_BUTTON, + GuiActivator.getResources().getI18NString( + "service.gui.MERGE_TO_CALL")); + + } + + if(isButtonEnabled(HIDE_CALL_RECORD_BUTON_PROP)) + { + recordButton = new RecordButton(aCall); + } + + if(isButtonEnabled(HIDE_TOGGLE_VIDEO_BUTON_PROP)) + { + showHideVideoButton = new ShowHideVideoButton(uiVideoHandler); + } + + if(isButtonEnabled(HIDE_CALL_TRANSFER_BUTON_PROP)) + { + transferCallButton = new TransferCallButton(aCall); + } + + if(isButtonEnabled(HIDE_VIDEO_BUTON_PROP)) + { + videoButton = new LocalVideoButton(aCall); + } + + if (isButtonEnabled(HIDE_PEERS_LIST_BUTON_PROP)) + { + // If the PEERS_LIST_HIDDEN_PROP isn't specified we show the list + // by default. + showHidePeersButton = new ShowHidePeersButton(this, + isButtonEnabled(PEERS_LIST_HIDDEN_PROP)); + } + + localLevel + = new InputVolumeControlButton( + aCall, + ImageLoader.MICROPHONE, + ImageLoader.MUTE_BUTTON, + true, + false); + remoteLevel + = new OutputVolumeControlButton( + callConference, + ImageLoader.VOLUME_CONTROL_BUTTON, + false, + true) + .getComponent(); + + /* + * Now that the buttons have been initialized, set their order indexes + * so that they get added in the correct order later on. + */ + initButtonIndexes(); + + chatButton.addActionListener(this); + if (conferenceButton != null) + conferenceButton.addActionListener(this); + if (dialButton != null) + dialButton.addActionListener(this); + if (crmButton != null) + crmButton.addActionListener(this); + if (infoButton != null) + infoButton.addActionListener(this); + if (mergeButton != null) + mergeButton.addActionListener(this); + + settingsPanel.add(chatButton); + if (conferenceButton != null) + settingsPanel.add(conferenceButton); + if (desktopSharingButton != null) + settingsPanel.add(desktopSharingButton); + if (dialButton != null) + settingsPanel.add(dialButton); + if (fullScreenButton != null) + settingsPanel.add(fullScreenButton); + + settingsPanel.add(hangupButton); + + if (holdButton != null) + settingsPanel.add(holdButton); + if (crmButton != null) + settingsPanel.add(crmButton); + if (infoButton != null) + settingsPanel.add(infoButton); + if (mergeButton != null) + settingsPanel.add(mergeButton); + if (recordButton != null) + settingsPanel.add(recordButton); + if (showHideVideoButton != null) + settingsPanel.add(showHideVideoButton); + if (mergeButton != null) + settingsPanel.add(transferCallButton); + if (videoButton != null) + settingsPanel.add(videoButton); + if (showHidePeersButton != null) + settingsPanel.add(showHidePeersButton); + + // The bottom bar will contain the settingsPanel. + add(createBottomBar(), BorderLayout.SOUTH); + } + + /** + * Tests a provided boolean property name, returning false if it should be + * hidden. + * + * Used in {@link #initializeUserInterfaceHierarchy()} + * @param buttonHidePropertyName the name of the boolean property to check. + * @return false if the button should be hidden, true otherwise. + * + */ + private boolean isButtonEnabled(String buttonHidePropertyName) + { + return !GuiActivator.getConfigurationService().getBoolean( + buttonHidePropertyName, + false); + } + + /** + * Returns true if the call timer has been started, otherwise + * returns false. + * @return true if the call timer has been started, otherwise + * returns false + */ + public boolean isCallTimerStarted() + { + return isCallTimerStarted; + } + + /** + * Checks if the contained call is a conference call. + * + * @return true if the contained Call is a conference + * call, otherwise - returns false. + */ + boolean isConference() + { + // If we're the focus of the conference. + if (callConference.isConferenceFocus()) + return true; + + // If one of our peers is a conference focus, we're in a + // conference call. + List callPeers = callConference.getCallPeers(); + + for (CallPeer callPeer : callPeers) + { + if (callPeer.isConferenceFocus()) + return true; + } + + // the call can have two peers at the same time and there is no one + // is conference focus. This is situation when someone has made an + // attended transfer and has transfered us. We have one call with two + // peers the one we are talking to and the one we have been transfered + // to. And the first one is been hanged up and so the call passes through + // conference call focus a moment and than go again to one to one call. + return callPeers.size() > 1; + } + + /** + * Determines whether this view is displayed in full-screen or windowed + * mode. + * + * @return true if this view is displayed in full-screen mode or + * false for windowed mode + */ + boolean isFullScreen() + { + return callWindow.isFullScreen(); + } + + /** + * Checks whether recording is currently enabled or not, state retrieved + * from call record button state. + * + * @return true if the recording is already started, false + * otherwise + */ + public boolean isRecordingStarted() + { + if (recordButton == null) + return false; + + return recordButton.isSelected(); + } + + /** + * Returns true if the show/hide video button is currently selected, + * false - otherwise. + * + * @return true if the show/hide video button is currently selected, + * false - otherwise + */ + public boolean isShowHideVideoButtonSelected() + { + return showHideVideoButton.isSelected(); + } + + /** + * Reloads icons. + */ + public void loadSkin() + { + if (dialButton != null) + { + dialButton.setBackgroundImage( + ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG)); + dialButton.setIconImage( + ImageLoader.getImage(ImageLoader.DIAL_BUTTON)); + } + + if (conferenceButton != null) + { + conferenceButton.setBackgroundImage( + ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG)); + conferenceButton.setIconImage( + ImageLoader.getImage(ImageLoader.ADD_TO_CALL_BUTTON)); + } + + if (hangupButton != null) + hangupButton.setBackgroundImage( + ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_BG)); + } + + /** + * Notifies this instance about a specific VideoEvent which may + * warrant {@link #ensureSize(Component, int, int)} to be invoked in order + * to try to have the associated visual Component displaying video + * shown without scaling. The method will execute on the AWT event + * dispatching thread because it will be making its judgments based on the + * properties of AWT Components. + * + * @param ev a VideoEvent which represents the cause of the + * notification and specifies the visual Component displaying video + * which may need an adjustment of a Frame's size in order to be displayed + * without scaling + */ + private void maybeEnsureSize(final VideoEvent ev) + { + if (!SwingUtilities.isEventDispatchThread()) + { + SwingUtilities.invokeLater( + new Runnable() + { + public void run() + { + maybeEnsureSize(ev); + } + }); + return; + } + + if (ev instanceof SizeChangeVideoEvent) + { + /* + * If a visual Component depicting video (streaming between the + * local peer/user and the remote peers) changes its size, try to + * adjust the size of the Frame which displays it so that it appears + * without scaling. + */ + SizeChangeVideoEvent scev = (SizeChangeVideoEvent) ev; + + ensureSize( + scev.getVisualComponent(), + scev.getWidth(), scev.getHeight()); + } + else if (ev.getType() == VideoEvent.VIDEO_ADDED) + { + Component video = ev.getVisualComponent(); + + if ((video != null) + && UIVideoHandler2.isAncestor(this, video) + && video.isPreferredSizeSet()) + { + Dimension prefSize = video.getPreferredSize(); + + if ((prefSize.height > 0) && (prefSize.width > 0)) + { + Dimension size = video.getSize(); + + if ((prefSize.height > size.height) + || (prefSize.width > size.width)) + { + ensureSize( + video, + prefSize.width, prefSize.height); + } + } + } + } + } + + /** + * Invoked by {@link #callConferenceListener} to notify this instance about + * an EventObject related to the CallConference depicted + * by this CallPanel, the Calls participating in it, + * the CallPeers associated with them, the + * ConferenceMembers participating in any telephony conferences + * organized by them, etc. In other words, notifies this instance about + * any change which may cause an update to be required so that this view + * i.e. CallPanel depicts the current state of its model i.e. + * {@link #callConference}. + * + * @param ev the EventObject this instance is being notified + * about. + */ + private void onCallConferenceEventObject(EventObject ev) + { + /* + * The main task is to invoke updateViewFromModel() in order to make + * sure that this view depicts the current state of its model. + */ + + try + { + /* + * However, we seem to be keeping track of the duration of the call + * (i.e. the telephony conference) in the user interface. Stop the + * Timer which ticks the duration of the call as soon as the + * telephony conference depicted by this instance appears to have + * ended. The situation will very likely occur when a Call is + * removed from the telephony conference or a CallPeer is removed + * from a Call. + */ + boolean tryStopCallTimer = false; + + if (ev instanceof CallPeerEvent) + { + tryStopCallTimer + = (CallPeerEvent.CALL_PEER_REMOVED + == ((CallPeerEvent) ev).getEventID()); + } + else if (ev instanceof PropertyChangeEvent) + { + PropertyChangeEvent pcev = (PropertyChangeEvent) ev; + + tryStopCallTimer + = (CallConference.CALLS.equals(pcev.getPropertyName()) + && (pcev.getOldValue() instanceof Call) + && (pcev.getNewValue() == null)); + } + if (tryStopCallTimer + && (callConference.isEnded() + || callConference.getCallPeerCount() == 0)) + { + stopCallTimer(); + } + } + finally + { + updateViewFromModel(); + } + } + + /** + * Notifies this CallPanel about a specific CallEvent + * (received by CallManager). The source Call may or may + * not be participating in the telephony conference depicted by this + * instance but allows it to update any state which may depend on the + * Calls which are established application-wide. + * + * @param ev a CallEvent which specifies the Call which + * caused this instance to be notified and the exact type of the + * notification event + */ + void onCallEvent(CallEvent ev) + { + updateMergeButtonState(); + } + + /** + * Adds/removes the Component of the PluginComponent + * specified by a PluginComponentEvent to/from + * {@link #settingsPanel} (if it is appropriate for this + * Container). + * + * @param ev a PluginComponentEvent which specifies the + * PluginComponent whose Component is to be added/removed + * to/from {@link #settingsPanel} + */ + protected void onPluginComponentEvent(PluginComponentEvent ev) + { + PluginComponentFactory pc = ev.getPluginComponentFactory(); + + if (pc.getContainer().equals( + net.java.sip.communicator.service.gui.Container + .CONTAINER_CALL_DIALOG)) + { + PluginComponent plugin = + pc.getPluginComponentInstance(CallPanel.this); + Component c = (Component)plugin.getComponent(); + plugin.setCurrentContact( + CallManager.getPeerMetaContact( + callConference.getCallPeers().get(0))); + + switch (ev.getEventID()) + { + case PluginComponentEvent.PLUGIN_COMPONENT_ADDED: + settingsPanel.add(c); + break; + case PluginComponentEvent.PLUGIN_COMPONENT_REMOVED: + settingsPanel.remove(c); + break; + } + + settingsPanel.revalidate(); + settingsPanel.repaint(); + } + } + + /** + * Indicates that the peer panel was added. + * + * @param ev the event. + */ + public void peerViewAdded(ConferencePeerViewEvent ev) {} + + /** + * Indicates that the peer panel was removed. + * + * @param ev the event. + */ + public void peerViewRemoved(ConferencePeerViewEvent ev) + { + updateViewFromModel(); + } + + /** + * {@inheritDoc} + * + * Adds the Component of the PluginComponent specified by + * the PluginComponentEvent to {@link #settingsPanel} (if it is + * appropriate for this Container). + */ + public void pluginComponentAdded(PluginComponentEvent ev) + { + onPluginComponentEvent(ev); + } + + /** + * {@inheritDoc} + * + * Removes the Component of the PluginComponent specified + * by the PluginComponentEvent from {@link #settingsPanel} (if it + * is appropriate for this Container). + */ + public void pluginComponentRemoved(PluginComponentEvent ev) + { + onPluginComponentEvent(ev); + } + + /** + * Removes the given CallTitleListener to the list of listeners, + * notified for call title changes. + * + * @param l the CallTitleListener to remove + */ + public void removeCallTitleListener(CallTitleListener l) + { + synchronized (titleListeners) + { + titleListeners.remove(l); + } + } + + /** + * Remove remote video specific components. + */ + public void removeRemoteVideoSpecificComponents() + { + if(resizeVideoButton != null) + settingsPanel.remove(resizeVideoButton); + + settingsPanel.revalidate(); + settingsPanel.repaint(); + } + + /** + * Sets the title of this dialog in accord with a specific time of start of + * the telephony call/conference depicted by this CallPanel. + * + * @param startTime the time in milliseconds at which the telephony + * call/conference depicted by this CallPanel is considered to have + * started + */ + private void setCallTitle(long startTime) + { + StringBuilder title = new StringBuilder(); + + if (startTime != 0) + { + title.append( + GuiUtils.formatTime( + startTime, + System.currentTimeMillis())); + title.append(" | "); + } + else + title.append("00:00:00 | "); + + List callPeers = callConference.getCallPeers(); + + if ((callPeers.size() > 0) + && (GuiActivator.getUIService().getSingleWindowContainer() + != null)) + { + title.append(callPeers.get(0).getDisplayName()); + } + else + { + title.append( + GuiActivator.getResources().getI18NString( + "service.gui.CALL")); + } + + this.title = title.toString(); + + fireTitleChangeEvent(); + } + + /** + * Sets the display of this view to full-screen or windowed mode. + * + * @param fullScreen true to display this view in full-screen mode + * or false for windowed mode + */ + void setFullScreen(boolean fullScreen) + { + callWindow.setFullScreen(fullScreen); + } + + /** + * Shows/hides the thumbnails list in the case of video conference. + * + * @param show true to show the thumbnails list, false + * to hide it + */ + public void showThumbnailsList(boolean show) + { + // This shouldn't happen, but if we aren't in a video conference we + // have nothing to do here. + if (!(callPanel instanceof VideoConferenceCallPanel)) + return; + + ((VideoConferenceCallPanel) callPanel).showThumbnailsList(show); + } + + /** + * Selects or unselects the video button in this call dialog. + * + * @param isSelected indicates if the video button should be selected or not + */ + public void setVideoButtonSelected(boolean isSelected) + { + if (isSelected && !videoButton.isSelected()) + videoButton.setSelected(true); + else if (!isSelected && videoButton.isSelected()) + videoButton.setSelected(false); + } + + /** + * Starts the timer that counts call duration. + */ + public void startCallTimer() + { + callConferenceStartTime = System.currentTimeMillis(); + callDurationTimer.start(); + isCallTimerStarted = true; + } + + /** + * Stops the timer that counts call duration. + */ + public void stopCallTimer() + { + this.callDurationTimer.stop(); + } + + /** + * Notifies this instance that {@link #uiVideoHandler} has reported a change + * in the video-related information which may warrant an update of this view + * from its model. + * + * @param arg an Object, if any, which represents the cause that + * triggered the notification + */ + private void uiVideoHandlerUpdate(Object arg) + { + /* The most important task is to update this view from its model. */ + + /* + * If a visual Component displaying video is reported to have been + * added/prepared/received, we may have to adjust the size of the Frame + * displaying this user interface so that the video appears without + * scaling. + */ + /* + * XXX The following may be making judgments about the user interface + * out of the AWT event dispatching thread which is a prerequisite for + * unexpected behavior. Anyway, that's the only idea at the time of this + * writing. + */ + VideoEvent maybeEnsureSize = null; + + if (arg instanceof VideoEvent) + { + try + { + VideoEvent vev = (VideoEvent) arg; + int vevType = vev.getType(); + + if (vevType == VideoEvent.VIDEO_ADDED) + { + Component video = vev.getVisualComponent(); + + if ((video != null) + && !UIVideoHandler2.isAncestor(this, video)) + { + maybeEnsureSize = vev; + } + } + else if (vevType == SizeChangeVideoEvent.VIDEO_SIZE_CHANGE) + { + /* + * If a visual Component depicting video (streaming between + * the local peer/user and the remote peers) changes its + * size, try to adjust the size of the Frame which displays + * it so that it appears without scaling. + */ + maybeEnsureSize = vev; + } + } + catch (Throwable t) + { + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else if (logger.isDebugEnabled()) + { + logger.debug( + "Failed to determine whether it is necessary to" + + " adjust a Frame's size in response to a" + + " VideoEvent.", + t); + } + } + } + + updateViewFromModel(); + + if (maybeEnsureSize != null) + { + /* + * The method maybeEnsureSize will execute on the AWT event + * dispatching thread. + */ + try + { + maybeEnsureSize(maybeEnsureSize); + } + catch (Throwable t) + { + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + { + logger.error( + "Failed to adjust a Frame's size" + + " in response to a VideoEvent.", + t); + } + } + } + } + + /** + * Updates the state of the general hold button. The hold button is selected + * only if all call peers are locally or mutually on hold at the same time. + * In all other cases the hold button is unselected. + */ + public void updateHoldButtonState() + { + // If the hold button has been disabled by its configuration property we + // have nothing more to do here. + if (holdButton == null) + return; + + if(!SwingUtilities.isEventDispatchThread()) + { + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + updateHoldButtonState(); + } + }); + + return; + } + + List peers = callConference.getCallPeers(); + boolean areAllPeersLocallyOnHold; + + if (peers.isEmpty()) + { + /* + * It feels natural to not have the holdButton selected when there + * are no peers. + */ + areAllPeersLocallyOnHold = false; + } + else + { + areAllPeersLocallyOnHold = true; + for (CallPeer peer : callConference.getCallPeers()) + { + CallPeerState state = peer.getState(); + + // If we have clicked the hold button in a full screen mode + // we need to update the state of the call dialog hold button. + if (!state.equals(CallPeerState.ON_HOLD_LOCALLY) + && !state.equals(CallPeerState.ON_HOLD_MUTUALLY)) + { + areAllPeersLocallyOnHold = false; + break; + } + } + } + + // If we have clicked the hold button in a full screen mode or selected + // hold of the peer menu in a conference call we need to update the + // state of the call dialog hold button. + holdButton.setSelected(areAllPeersLocallyOnHold); + } + + /** + * Updates the visible state/property of {@link #mergeButton} if + * the merge button is present. + */ + private void updateMergeButtonState() + { + // If the merge button isn't present, for example if it's hidden by + // its configuration property we have nothing more to do here. + if (mergeButton == null) + return; + + List conferences = new ArrayList(); + int cpt = 0; + + for (Call call : CallManager.getInProgressCalls()) + { + CallConference conference = call.getConference(); + + if (conference == null) + cpt++; + else if (!conferences.contains(conference)) + { + conferences.add(conference); + cpt++; + } + else + continue; + + if (cpt > 1) + break; + } + + mergeButton.setVisible(cpt > 1); + } + + /** + * Updates {@link #settingsPanel} from the model of this view. The update is + * performed in the AWT event dispatching thread. + *

+ * The center of this view is occupied by {@link #callPanel}, the bottom of + * this view is dedicated to settingsPanel. The method + * {@link #updateViewFromModelInEventDispatchThread()} updates + * callPanel from the model of this view and then invokes the + * method updateSettingsPanelInEventDispatchThread(). Thus this + * whole view is updated so that it depicts the current state of its model. + *

+ * + * @param callConferenceIsEnded true if the method + * updateViewFromModelInEventDispatchThread() considers the + * {@link #callConference} ended; otherwise, false. When the + * callConference is considered ended, the callPanel + * instance will not be switched to a specific type (one-to-one, audio-only, + * or audio/video) because, otherwise, the switch will leave it + * null and this view will remain blank. In such a case, + * settingsPanel may wish to do pretty much the same but disable + * and/or hide the buttons it contains. + */ + private void updateSettingsPanelInEventDispatchThread( + boolean callConferenceIsEnded) + { + /* + * XXX The method directly delegates to the method + * doUpdateSettingsPanelInEventDispatchThread at the time of this + * writing which may be considered a waste. But in the fashion of the + * method updateViewFromModelInEventDispatchThread we have made it easy + * to add code before and/or after the invocation of the delegate. + */ + doUpdateSettingsPanelInEventDispatchThread(callConferenceIsEnded); + } + + /** + * Updates this view i.e. CallPanel so that it depicts the current + * state of its model i.e. callConference. + */ + 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. + */ + if (!disposed) + { + if (SwingUtilities.isEventDispatchThread()) + updateViewFromModelInEventDispatchThread(); + else + { + SwingUtilities.invokeLater( + updateViewFromModelInEventDispatchThread); + } + } + } + + /** + * Updates this view i.e. CallPanel so that it depicts the current + * state of its model i.e. callConference. 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. + */ + if (disposed) + return; + + /* + * We may add, remove, show, and hide various Components of the user + * interface hierarchy of this instance bellow. Consequently, this view + * may become larger in width and/or height than its current Frame has + * dedicated to it. Try to detect such cases and attempt to adjust the + * Frame's size accordingly. + */ + Dimension oldPrefSize = getPreferredSize(); + + doUpdateViewFromModelInEventDispatchThread(); + + /* + * We may have added, removed, shown, and hidden various Components of + * the user interface hierarchy of this instance above. Consequently, + * this view may have become larger in width and/or height than its + * current Frame has dedicated to it. Try to detect such cases and + * attempt to adjust the Frame's size accordingly. + */ + Dimension newPrefSize = getPreferredSize(); + + if ((newPrefSize != null) + && ((newPrefSize.height > getHeight()) + || (newPrefSize.width > getWidth()))) + { + int oldPrefHeight, oldPrefWidth; + + if (oldPrefSize == null) + { + oldPrefHeight = 0; + oldPrefWidth = 0; + } + else + { + oldPrefHeight = oldPrefSize.height; + oldPrefWidth = oldPrefSize.width; + } + if ((newPrefSize.height != oldPrefHeight) + || (newPrefSize.width != oldPrefWidth)) + { + ensureSize( + this, + newPrefSize.width, newPrefSize.height); + } + } + } + + /** + * Listens for contact status changes and updates the image of the + * chat message button. + * @param evt the ContactPresenceStatusChangeEvent describing the status + */ + @Override + public void contactPresenceStatusChanged(ContactPresenceStatusChangeEvent evt) + { + Contact contact = getIMCapableCallPeers(1).get(0); + + if(contact != null && contact.equals(evt.getSourceContact())) + { + chatButton.setIconImage( + Constants.getMessageStatusIcon(contact.getPresenceStatus())); + chatButton.repaint(); + } + } + + /** + * Implements the listener which listens to events fired by the + * CallConference depicted by this instance, the Calls + * participating in that telephony conference, the CallPeers + * associated with those Calls and the ConferenceMembers + * participating in the telephony conferences organized by those + * CallPeers. Updates this view i.e. CallPanel so that it depicts + * the current state of its model i.e. {@link #callConference}. + */ + private class CallConferenceListener + extends CallPeerConferenceAdapter + implements CallChangeListener, + PropertyChangeListener + { + /** + * {@inheritDoc} + * + * Invokes {@link #onCallPeerEvent(CallPeerEvent)} because the + * CallPeerEvent allows distinguishing whether a + * CallPeer was added or removed by examining its + * eventID. + */ + public void callPeerAdded(CallPeerEvent ev) + { + onCallPeerEvent(ev); + } + + /** + * {@inheritDoc} + * + * Invokes {@link #onCallPeerEvent(CallPeerEvent)} because the + * CallPeerEvent allows distinguishing whether a + * CallPeer was added or removed by examining its + * eventID. + */ + public void callPeerRemoved(CallPeerEvent ev) + { + onCallPeerEvent(ev); + } + + /** + * {@inheritDoc} + * + * Invokes {@link #onEventObject(EventObject)}. + */ + public void callStateChanged(CallChangeEvent ev) + { + onEventObject(ev); + } + + /** + * {@inheritDoc} + * + * Invokes {@link #onEventObject(EventObject)}. + */ + @Override + protected void onCallPeerConferenceEvent(CallPeerConferenceEvent ev) + { + onEventObject(ev); + } + + /** + * Notifies this CallChangeListener about a specific + * CallPeerEvent i.e. that a CallPeer was added to or + * removed from a Call. Invokes + * {@link #onEventObject(EventObject)}. + * + * @param ev the CallPeerEvent to notify this + * CallChangeListener about i.e. which specifies the + * CallPeer which was added/removed and the Call + * to/from which it was added/removed + */ + private void onCallPeerEvent(CallPeerEvent ev) + { + onEventObject(ev); + } + + /** + * Invoked by the various listener method implementations provided by + * this CallConferenceListener to notify this instance about an + * EventObject related to the CallConference depicted + * by this CallPanel, the Calls participating in it, + * the CallPeers associated with them, the + * ConferenceMembers participating in any telephony conferences + * organized by them, etc. In other words, notifies this instance about + * any change which may cause an update to be required so that this view + * i.e. CallPanel depicts the current state of its model i.e. + * {@link CallPanel#callConference}. + * + * @param ev the EventObject this instance is being notified + * about. + */ + private void onEventObject(EventObject ev) + { + onCallConferenceEventObject(ev); + } + + /** + * {@inheritDoc} + * + * Invokes {@link #onEventObject(EventObject)}. + */ + public void propertyChange(PropertyChangeEvent ev) + { + String propertyName = ev.getPropertyName(); + + /* + * If a Call is added to or removed from the CallConference depicted + * by this CallPanel, an update of the view from its model will most + * likely be required. + */ + if (propertyName.equals(CallConference.CALLS)) + { + onEventObject(ev); + } + else if (propertyName.equals(CallContainer.PROP_FULL_SCREEN)) + { + if (ev.getSource().equals(callWindow.getFrame())) + { + try + { + /* + * We'll turn the switching between full-screen and + * windowed mode into a model state because a + * significant part of this view changes upon such a + * switch. + */ + onEventObject(ev); + } + finally + { + callWindowPropertyChange(ev); + } + } + } + } + } +} diff --git a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java index 77edeed04..152c3a595 100644 --- a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java +++ b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java @@ -890,6 +890,12 @@ public class ImageLoader public static final ImageID CALL_INFO = new ImageID("service.gui.buttons.CALL_INFO"); + /** + * The call information button icon used in the call panel. + */ + public static final ImageID CRM = + new ImageID("service.gui.buttons.CRM"); + /** * The image used, when a contact has no photo specified. */