/* * 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.presence; import java.awt.*; import java.awt.event.*; import java.beans.*; import javax.swing.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.lookandfeel.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.desktoputil.presence.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.globalstatus.*; import net.java.sip.communicator.service.systray.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.account.*; import org.jitsi.util.*; /** * The <tt>GlobalStatusSelectorBox</tt> is a global status selector box, which * appears in the status panel, when the user has more than one account. It * allows to the user to change globally its status with only one click, instead * of going through all accounts and selecting the desired status for every one * of them. * <p> * By default the <tt>GlobalStatusSelectorBox</tt> will show the most connected * status of all registered accounts. * * @author Yana Stamcheva * @author Lubomir Marinov * @author Adam Netocny */ public class GlobalStatusSelectorBox extends StatusSelectorMenu implements ActionListener { /** * Class id key used in UIDefaults. */ private static final String uiClassID = GlobalStatusSelectorBox.class.getName() + "StatusMenuUI"; /** * Adds the ui class to UIDefaults. */ static { UIManager.getDefaults().put(uiClassID, SIPCommStatusMenuUI.class.getName()); } /** * The indent of the image. */ private static final int IMAGE_INDENT = 10; /** * Property that controls whether we hide or show global status message * menu. */ private static final String HIDE_GLOBAL_STATUS_MESSAGE = "net.java.sip.communicator.impl.gui.main.HIDE_GLOBAL_STATUS_MESSAGE"; /** * The arrow icon shown on the right of the status and indicating that * this is a menu. */ private Image arrowImage = ImageLoader.getImage(ImageLoader.DOWN_ARROW_ICON); /** * The width of the text. */ private int textWidth = 0; /** * Indicates if this is the first account added. */ private boolean isFirstAccount = true; /** * Take care for global status items, that only one is selected. */ private ButtonGroup group = new ButtonGroup(); /** * The global status message menu. */ private GlobalStatusMessageMenu globalStatusMessageMenu = null; /** * The parent panel that creates us. */ private final AccountStatusPanel accountStatusPanel; /** * The index of the first status, the online one. */ private int firstStatusIndex = -1; /** * Creates an instance of <tt>SimpleStatusSelectorBox</tt>. */ public GlobalStatusSelectorBox(AccountStatusPanel accountStatusPanel) { super(); this.accountStatusPanel = accountStatusPanel; JLabel titleLabel = new JLabel(GuiActivator.getResources() .getI18NString("service.gui.SET_GLOBAL_STATUS")); titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0)); titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD)); this.add(titleLabel); this.addSeparator(); GlobalStatusEnum offlineStatus = GlobalStatusEnum.ONLINE; group.add(createMenuItem(offlineStatus, -1)); firstStatusIndex = getItemCount(); if(isPresenceOpSetForProvidersAvailable()) addAvailableStatuses(); group.add(createMenuItem(GlobalStatusEnum.OFFLINE, -1)); if(!GuiActivator.getConfigurationService() .getBoolean(HIDE_GLOBAL_STATUS_MESSAGE, false) && isPresenceOpSetForProvidersAvailable()) { this.addSeparator(); globalStatusMessageMenu = new GlobalStatusMessageMenu(true); globalStatusMessageMenu.addPropertyChangeListener( new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if(evt.getPropertyName().equals( GlobalStatusMessageMenu. STATUS_MESSAGE_UPDATED_PROP)) { changeTooltip((String)evt.getNewValue()); } } }); this.add((JMenu)globalStatusMessageMenu.getMenu()); } if(!ConfigurationUtils.isHideAccountStatusSelectorsEnabled()) this.addSeparator(); this.setFont(titleLabel.getFont().deriveFont(Font.PLAIN, 11f)); if(offlineStatus != null) this.setIcon(new ImageIcon(offlineStatus.getStatusIcon())); this.setIconTextGap(2); this.setOpaque(false); this.setText("Offline"); changeTooltip(null); fitSizeToText(); } /** * Adds the available global statuses. All the statuses except ONLINE and * OFFLINE, those that will be inserted between them. * Check first whether the statuses are not already inserted. */ private void addAvailableStatuses() { if(hasAvailableStatuses()) return; int index = firstStatusIndex; // creates menu item entry for every global status // except ONLINE and OFFLINE for(GlobalStatusEnum status : GlobalStatusEnum.globalStatusSet) { if(status.equals(GlobalStatusEnum.OFFLINE) || status.equals(GlobalStatusEnum.ONLINE)) continue; group.add(createMenuItem(status, index++)); } } /** * Removes the available global statuses (those except ONLINE and OFFLINE) */ private void removeAvailableStatuses() { // removes menu item entry for every global status // except ONLINE and OFFLINE for(GlobalStatusEnum status : GlobalStatusEnum.globalStatusSet) { if(status.equals(GlobalStatusEnum.OFFLINE) || status.equals(GlobalStatusEnum.ONLINE)) continue; JCheckBoxMenuItem item = getItemFromStatus(status); if(item == null) continue; group.remove(item); this.remove(item); } } /** * Check for available statuses we have in the menu. * All except ONLINE and OFFLINE one. * @return */ private boolean hasAvailableStatuses() { // check menu item entries for every global status // except ONLINE and OFFLINE for(GlobalStatusEnum status : GlobalStatusEnum.globalStatusSet) { if(status.equals(GlobalStatusEnum.OFFLINE) || status.equals(GlobalStatusEnum.ONLINE)) continue; if(getItemFromStatus(status) != null) return true; } return false; } /** * Changes the tooltip to default or the current set status message. * @param message */ private void changeTooltip(String message) { if(StringUtils.isNullOrEmpty(message)) { if(globalStatusMessageMenu != null) globalStatusMessageMenu.clearSelectedItems(); this.setToolTipText("<html><b>" + GuiActivator.getResources() .getI18NString("service.gui.SET_GLOBAL_STATUS") + "</b></html>"); accountStatusPanel.setStatusMessage(null); } else { this.setToolTipText("<html><b>" + message + "</b></html>"); accountStatusPanel.setStatusMessage(message); } } /** * Creates a menu item with the given <tt>textKey</tt>, <tt>iconID</tt> and * <tt>name</tt>. * * @param status the global status * @param index index the position in the container's list at which to * insert the status, where <code>-1</code> means append to the end * @return the created <tt>JCheckBoxMenuItem</tt> */ private JCheckBoxMenuItem createMenuItem( GlobalStatusEnum status, int index) { JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem( GlobalStatusEnum.getI18NStatusName(status), new ImageIcon(status.getStatusIcon())); menuItem.setName(status.getStatusName()); menuItem.addActionListener(this); if(index == -1) add(menuItem); else add(menuItem, index); return menuItem; } /** * Adds a status menu for the account given by <tt>protocolProvider</tt>. * @param protocolProvider the <tt>ProtocolProviderService</tt>, for which * to add a status menu */ public void addAccount(ProtocolProviderService protocolProvider) { if (protocolProvider.getAccountID().isHidden()) return; OperationSetPersistentPresence presenceOpSet = (OperationSetPersistentPresence) protocolProvider.getOperationSet(OperationSetPresence.class); JMenuItem itemToAdd; if(protocolProvider.getAccountID().isStatusMenuHidden()) { itemToAdd = new ReadonlyStatusItem(protocolProvider); } else { itemToAdd = (presenceOpSet != null) ? new PresenceStatusMenu(protocolProvider) : new SimpleStatusMenu(protocolProvider); } if(ConfigurationUtils.isHideAccountStatusSelectorsEnabled()) itemToAdd.setVisible(false); // If this is the first account in our menu. if (isFirstAccount) { add(itemToAdd); isFirstAccount = false; // if we have a provider with opset presence add available statuses if(presenceOpSet != null) addAvailableStatuses(); return; } boolean isMenuAdded = false; AccountID accountId = protocolProvider.getAccountID(); // If we already have other accounts. for (Component c : getPopupMenu().getComponents()) { if (!(c instanceof StatusEntry)) continue; StatusEntry menu = (StatusEntry) c; int menuIndex = getPopupMenu().getComponentIndex( menu.getEntryComponent()); AccountID menuAccountID = menu.getProtocolProvider().getAccountID(); int protocolCompare = accountId.getProtocolDisplayName().compareTo( menuAccountID.getProtocolDisplayName()); // If the new account protocol name is before the name of the menu // we insert the new account before the given menu. if (protocolCompare < 0) { insert(itemToAdd, menuIndex); isMenuAdded = true; break; } else if (protocolCompare == 0) { // If we have the same protocol name, we check the account name. if (accountId.getDisplayName() .compareTo(menuAccountID.getDisplayName()) < 0) { insert( itemToAdd, menuIndex); isMenuAdded = true; break; } } } if (!isMenuAdded) add(itemToAdd); // if we have a provider with opset presence add available statuses if(presenceOpSet != null) addAvailableStatuses(); } /** * Removes the status menu corresponding to the account given by * <tt>protocolProvider</tt>. * @param protocolProvider the <tt>ProtocolProviderService</tt>, which * menu to remove */ public void removeAccount(ProtocolProviderService protocolProvider) { StatusEntry menu = getStatusEntry(protocolProvider); if (menu != null) { menu.dispose(); remove(menu.getEntryComponent()); } // if we do not have provider with presence opset // remove available statuses if(!isPresenceOpSetForProvidersAvailable()) removeAvailableStatuses(); } /** * Check the available providers for operation set presence. * @return do we have a provider with opset presence. */ private boolean isPresenceOpSetForProvidersAvailable() { for (Component c : getPopupMenu().getComponents()) { if(!(c instanceof StatusEntry)) continue; StatusEntry menu = (StatusEntry) c; if(menu.getProtocolProvider() .getOperationSet(OperationSetPresence.class) != null) return true; } return false; } /** * Checks if a menu for the given <tt>protocolProvider</tt> exists. * @param protocolProvider the <tt>ProtocolProviderService</tt> to check * @return <tt>true</tt> to indicate that a status menu for the given * <tt>protocolProvider</tt> already exists, otherwise returns * <tt>false</tt> */ public boolean containsAccount(ProtocolProviderService protocolProvider) { return getStatusEntry(protocolProvider) != null; } /** * Starts connecting user interface for the given <tt>protocolProvider</tt>. * @param protocolProvider the <tt>ProtocolProviderService</tt> to start * connecting for */ public void startConnecting(ProtocolProviderService protocolProvider) { StatusEntry menu = getStatusEntry(protocolProvider); if (menu != null) menu.startConnecting(); } /** * Stops connecting user interface for the given <tt>protocolProvider</tt>. * @param protocolProvider the <tt>ProtocolProviderService</tt> to stop * connecting for */ public void stopConnecting(ProtocolProviderService protocolProvider) { StatusEntry menu = getStatusEntry(protocolProvider); if (menu != null) menu.stopConnecting(); } /** * Returns <tt>true</tt> if there are selected status selector boxes, * otherwise returns <tt>false</tt>. * @return <tt>true</tt> if there are selected status selector boxes, * otherwise returns <tt>false</tt> */ public boolean hasSelectedMenus() { for (Component c : getComponents()) { if (!(c instanceof StatusEntry)) continue; StatusEntry menu = (StatusEntry) c; if (menu.isSelected()) return true; } return false; } /** * Handles the <tt>ActionEvent</tt> triggered when one of the items * in the list is selected. * @param e the <tt>ActionEvent</tt> that notified us */ public void actionPerformed(ActionEvent e) { JMenuItem menuItem = (JMenuItem) e.getSource(); String itemName = menuItem.getName(); if(GuiActivator.getGlobalStatusService() != null) { GuiActivator.getGlobalStatusService().publishStatus( GlobalStatusEnum.getStatusByName(itemName)); } } /** * Updates the status of the given <tt>protocolProvider</tt>. * * @param protocolProvider the <tt>ProtocolProviderService</tt> * corresponding to the menu to update */ public void updateStatus(ProtocolProviderService protocolProvider) { StatusEntry accountMenu = getStatusEntry(protocolProvider); if (accountMenu == null) return; PresenceStatus presenceStatus; if (!protocolProvider.isRegistered()) presenceStatus = accountMenu.getOfflineStatus(); else { presenceStatus = AccountStatusUtils.getPresenceStatus(protocolProvider); if (presenceStatus == null) presenceStatus = accountMenu.getOnlineStatus(); } accountMenu.updateStatus(presenceStatus); accountMenu.repaint(); this.updateGlobalStatus(); } /** * Updates the status of the given <tt>protocolProvider</tt> with the given * <tt>presenceStatus</tt>. * @param protocolProvider the <tt>ProtocolProviderService</tt> * corresponding to the menu to update * @param presenceStatus the new status to set */ public void updateStatus(ProtocolProviderService protocolProvider, PresenceStatus presenceStatus) { StatusEntry accountMenu = getStatusEntry(protocolProvider); if (accountMenu == null) return; accountMenu.updateStatus(presenceStatus); this.updateGlobalStatus(); } /** * Updates the global status by picking the most connected protocol provider * status. */ private void updateGlobalStatus() { if(GuiActivator.getGlobalStatusService() == null) return; PresenceStatus globalStatus = GuiActivator.getGlobalStatusService().getGlobalPresenceStatus(); JCheckBoxMenuItem item = getItemFromStatus(globalStatus); item.setSelected(true); setSelected(new SelectedObject(item.getText(), item.getIcon(), item)); fitSizeToText(); this.revalidate(); setSystrayIcon(globalStatus); if(!globalStatus.isOnline()) { changeTooltip(null); } } /** * Sets the systray icon corresponding to the given status. * * @param globalStatus the status, for which we're setting the systray icon. */ private void setSystrayIcon(PresenceStatus globalStatus) { SystrayService trayService = GuiActivator.getSystrayService(); if(trayService == null) return; int imgType = SystrayService.SC_IMG_OFFLINE_TYPE; if (globalStatus.equals(GlobalStatusEnum.OFFLINE)) { imgType = SystrayService.SC_IMG_OFFLINE_TYPE; } else if (globalStatus.equals(GlobalStatusEnum.DO_NOT_DISTURB)) { imgType = SystrayService.SC_IMG_DND_TYPE; } else if (globalStatus.equals(GlobalStatusEnum.AWAY)) { imgType = SystrayService.SC_IMG_AWAY_TYPE; } else if (globalStatus.equals(GlobalStatusEnum.EXTENDED_AWAY)) { imgType = SystrayService.SC_IMG_EXTENDED_AWAY_TYPE; } else if (globalStatus.equals(GlobalStatusEnum.ONLINE)) { imgType = SystrayService.SC_IMG_TYPE; } else if (globalStatus.equals(GlobalStatusEnum.FREE_FOR_CHAT)) { imgType = SystrayService.SC_IMG_FFC_TYPE; } trayService.setSystrayIcon(imgType); } /** * Returns the <tt>JCheckBoxMenuItem</tt> corresponding to the given status. * For status constants we use here the values defined in the * <tt>PresenceStatus</tt>, but this is only for convenience. * * @param globalStatus the status to which the item should correspond * @return the <tt>JCheckBoxMenuItem</tt> corresponding to the given status */ private JCheckBoxMenuItem getItemFromStatus(PresenceStatus globalStatus) { for(Component c : getMenuComponents()) { if(c instanceof JCheckBoxMenuItem && globalStatus.getStatusName().equals(c.getName())) { return (JCheckBoxMenuItem) c; } } return null; } /** * Overwrites the <tt>paintComponent(Graphics g)</tt> method in order to * provide a new look and the mouse moves over this component. * @param g the <tt>Graphics</tt> object used for painting */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); if (textWidth != 0) { g = g.create(); try { AntialiasingManager.activateAntialiasing(g); g.drawImage( arrowImage, textWidth + 2*IMAGE_INDENT + 2, getX() + (this.getHeight() - arrowImage.getHeight(null)) / 2 + 1, null); } finally { g.dispose(); } } } /** * Computes the width of the text in pixels in order to position the arrow * during its painting. */ private void fitSizeToText() { String text = getText(); textWidth = (text == null) ? 0 : ComponentUtils.getStringWidth(this, text); this.setPreferredSize(new Dimension( textWidth + 2*IMAGE_INDENT + arrowImage.getWidth(null) + 5, 20)); } /** * Returns the <tt>StatusEntry</tt> corresponding to the given * <tt>protocolProvider</tt>. * @param protocolProvider the <tt>ProtocolProviderService</tt>, which * corresponding menu we're looking for * @return the <tt>StatusEntry</tt> corresponding to the given * <tt>protocolProvider</tt> */ private StatusEntry getStatusEntry(ProtocolProviderService protocolProvider) { for (Component c : getPopupMenu().getComponents()) { if (!(c instanceof StatusEntry)) continue; StatusEntry menu = (StatusEntry) c; if (menu.getProtocolProvider() != null && menu.getProtocolProvider().equals(protocolProvider)) return menu; } return null; } /** * Loads all icons and updates global status. */ @Override public void loadSkin() { super.loadSkin(); arrowImage = ImageLoader.getImage(ImageLoader.DOWN_ARROW_ICON); updateGlobalStatus(); } /** * Returns the name of the L&F class that renders this component. * * @return the string "TreeUI" * @see JComponent#getUIClassID * @see UIDefaults#getUI */ @Override public String getUIClassID() { return uiClassID; } /** * Not used. * @param presenceStatus the <tt>PresenceStatus</tt> to be selected in this */ @Override public void updateStatus(PresenceStatus presenceStatus) {} }