/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.gui.main.call; import java.awt.*; import java.awt.event.*; import java.beans.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.resources.*; import net.java.sip.communicator.util.skin.*; /** * The base class for all toggle buttons which control the call from the UI. * Allows extending buttons to focus on performing their toggle actions. * * @author Dmitri Melnikov * @author Adam Netocny * @author Yana Stamcheva * @author Lyubomir Marinov */ public abstract class AbstractCallToggleButton extends SIPCommToggleButton implements Skinnable { /** * The button model of this call toggle button. */ private class CallToggleButtonModel extends ToggleButtonModel implements ActionListener, Runnable { private Thread runner; public CallToggleButtonModel(Call call) { addActionListener(this); } public synchronized void actionPerformed(ActionEvent event) { if(spawnActionInNewThread) { if (runner == null) { runner = new Thread(this, LocalVideoButton.class.getName()); runner.setDaemon(true); setEnabled(false); runner.start(); } } else buttonPressed(); } private void doRun() { buttonPressed(); } public void run() { try { doRun(); } finally { synchronized (this) { if (Thread.currentThread().equals(runner)) { runner = null; setEnabled(true); } } } } } /** * The background image. */ protected ImageID bgImageID; /** * The rollover image */ protected ImageID bgRolloverImageID; /** * The <tt>Call</tt> that this button controls. */ protected final Call call; /** * The <tt>CallPanel</tt> which is the current ancestor of this instance and * which, for example, represents the full-screen display state of this * instance. */ private CallPanel callPanel; /** * The <tt>PropertyChangeListener</tt> which listens to {@link #callPanel} * about changes to the values of its properties such as * {@link CallContainer#PROP_FULL_SCREEN}. */ private final PropertyChangeListener callPanelPropertyChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { AbstractCallToggleButton.this.callPanelPropertyChange(ev); } }; /** * The indicator which determines whether this instance is displayed in * full-screen or windowed mode. */ private boolean fullScreen = false; /** * The icon image. */ protected ImageID iconImageID; /** * The pressed icon image. */ protected ImageID pressedIconImageID; /** * The pressed image. */ protected ImageID pressedImageID; private final boolean settingsPanel; /** * Whether we should spawn action when clicking the button in new thread. * Volume control buttons use this abstract button for its fullscreen view * and don't need the new thread. Default is true, create new thread. */ private boolean spawnActionInNewThread = true; /** * Initializes a new <tt>AbstractCallToggleButton</tt> instance which is to * control a toggle action for a specific <tt>Call</tt>. * * @param call the <tt>Call</tt> to be controlled by the instance * @param settingsPanel indicates if this button is added in the settings * panel on the bottom of the call window * @param selected <tt>true</tt> if the new toggle button is to be initially * selected; otherwise, <tt>false</tt> * @param iconImageID the <tt>ImageID</tt> of the image to be used as the * icon of the new instance * @param pressedIconImageID the <tt>ImageID</tt> of the image to be used * as the icon in the pressed button state of the new instance * @param toolTipTextKey the key in the <tt>ResourceManagementService</tt> * of the internationalized string which is to be used as the tool tip text * of the new instance */ protected AbstractCallToggleButton( Call call, boolean settingsPanel, boolean selected, ImageID iconImageID, ImageID pressedIconImageID, String toolTipTextKey) { this.call = call; this.iconImageID = iconImageID; this.pressedIconImageID = pressedIconImageID; this.settingsPanel = settingsPanel; if(settingsPanel) { bgRolloverImageID = ImageLoader.CALL_SETTING_BUTTON_BG; pressedImageID = ImageLoader.CALL_SETTING_BUTTON_PRESSED_BG; } else { bgImageID = ImageLoader.SOUND_SETTING_BUTTON_BG; bgRolloverImageID = ImageLoader.SOUND_SETTING_BUTTON_BG; pressedImageID = ImageLoader.SOUND_SETTING_BUTTON_PRESSED; } if (toolTipTextKey != null) { setToolTipText( GuiActivator.getResources().getI18NString(toolTipTextKey)); } setModel(new CallToggleButtonModel(call)); /* * AbstractCallToggleButton is dependent on whether it is displayed in * full-screen or windowed mode. Because AbstractCallToggleButton is * associated with a Call and because we know Calls are depicted by * CallPanels, keep track of the CallPanel in which this instance is * added and use the full-screen state of the CallPanel. */ addHierarchyListener( new HierarchyListener() { public void hierarchyChanged(HierarchyEvent ev) { AbstractCallToggleButton.this.hierarchyChanged(ev); } }); hierarchyChanged(null); setSelected(selected); // All items are now instantiated and could safely load the skin. loadSkin(); } /** * Initializes a new <tt>AbstractCallToggleButton</tt> instance which is to * control a toggle action for a specific <tt>Call</tt>. * * @param call the <tt>Call</tt> to be controlled by the instance * @param selected <tt>true</tt> if the new toggle button is to be initially * selected; otherwise, <tt>false</tt> * @param iconImageID the <tt>ImageID</tt> of the image to be used as the * icon of the new instance * @param toolTipTextKey the key in the <tt>ResourceManagementService</tt> * of the internationalized string which is to be used as the tool tip text * of the new instance */ protected AbstractCallToggleButton( Call call, boolean selected, ImageID iconImageID, String toolTipTextKey) { this(call, true, selected, iconImageID, null, toolTipTextKey); } /** * Notifies this <tt>AbstractCallToggleButton</tt> that its associated * action has been performed and that it should execute its very logic. */ public abstract void buttonPressed(); /** * Notifies this instance about a <tt>PropertyChangeEvent</tt> fired by * {@link #callPanel}. * * @param ev the <tt>PropertyChangeEvent</tt> fired by <tt>callPanel</tt> to * notify this instance about */ private void callPanelPropertyChange(PropertyChangeEvent ev) { if ((ev == null) || CallContainer.PROP_FULL_SCREEN.equals(ev.getPropertyName())) { boolean fullScreen = (callPanel == null) ? false : callPanel.isFullScreen(); if (this.fullScreen != fullScreen) { this.fullScreen = fullScreen; loadSkin(); } } } /** * Notifies this instance that the UI hierarchy that it belongs to has been * changed. * * @param ev an <tt>HierarchyEvent</tt> which identifies the specific of the * change in the UI hierarchy that this instance belongs */ private void hierarchyChanged(HierarchyEvent ev) { /* * Keep track of the CallPanel which is the current ancestor of this * instance and its full-screen display state. */ CallPanel callPanel = null; for (Container parent = getParent(); parent != null; parent = parent.getParent()) { if (parent instanceof CallPanel) { callPanel = (CallPanel) parent; break; } } if (this.callPanel != callPanel) { if (this.callPanel != null) { this.callPanel.removePropertyChangeListener( CallContainer.PROP_FULL_SCREEN, callPanelPropertyChangeListener); } this.callPanel = callPanel; if (this.callPanel != null) { this.callPanel.addPropertyChangeListener( CallContainer.PROP_FULL_SCREEN, callPanelPropertyChangeListener); } /* * If the callPanel instance has changed, then the full-screen * display state that it represents may have changed from the point * of view of this instance. */ callPanelPropertyChange(null); } } /** * Determines whether this instance is displayed in full-screen or windowed * mode. * * @return <tt>true</tt> if this instance is displayed in full-screen mode * or <tt>false</tt> for windowed mode */ protected boolean isFullScreen() { return fullScreen; } /** * Loads images. */ public void loadSkin() { int width = CallToolBarButton.DEFAULT_WIDTH; int height = CallToolBarButton.DEFAULT_HEIGHT; if (bgImageID != null) { Image bgImage = ImageLoader.getImage(bgImageID); setBgImage(bgImage); width = bgImage.getWidth(this); height = bgImage.getHeight(this); } setPreferredSize(new Dimension(width, height)); setMaximumSize(new Dimension(width, height)); setMinimumSize(new Dimension(width, height)); setBgRolloverImage(ImageLoader.getImage(bgRolloverImageID)); setPressedImage(ImageLoader.getImage(pressedImageID)); boolean fullScreen = isFullScreen(); if (iconImageID != null) { Image iconImage = ImageLoader.getImage(iconImageID); if (!fullScreen && !settingsPanel) { iconImage = ImageUtils.scaleImageWithinBounds(iconImage, 18, 18); } setIconImage(iconImage); } if (pressedIconImageID != null) { Image iconImage = ImageLoader.getImage(pressedIconImageID); if (!fullScreen && !settingsPanel) { iconImage = ImageUtils.scaleImageWithinBounds(iconImage, 18, 18); } setPressedIconImage(iconImage); } } /** * Sets the icon image of this button. * * @param iconImageID the identifier of the icon image */ public void setIconImageID(ImageID iconImageID) { this.iconImageID = iconImageID; Image iconImage = ImageLoader.getImage(iconImageID); if (!isFullScreen() && !settingsPanel) iconImage = ImageUtils.scaleImageWithinBounds(iconImage, 18, 18); setIconImage(iconImage); } /** * Changes behavior, whether we should start new thread for executing * actions, buttonPressed(). * * @param spawnActionInNewThread */ public void setSpawnActionInNewThread(boolean spawnActionInNewThread) { this.spawnActionInNewThread = spawnActionInNewThread; } }