/* * 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.chat; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; 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.service.replacement.smilies.*; import net.java.sip.communicator.util.skin.*; /** * The SmileysSelectorBox is the component where user could choose a * smiley icon to send. * * @author Yana Stamcheva * @author Lubomir Marinov * @author Adam Netocny */ public class SmileysSelectorBox extends SIPCommMenu implements ActionListener, MouseListener, PopupMenuListener, Skinnable { /** * The chat write panel. */ private ChatPanel chatPanel; /** * The smiley text label. */ private final JLabel smileyTextLabel = new JLabel(); /** * The smiley description label. */ private final JLabel smileyDescriptionLabel = new JLabel(); /** * The smilies service. */ private final SmiliesReplacementService smiliesService; /** * PopupMenu */ private JPopupMenu popupMenu; /** * Initializes a new SmileysSelectorBox instance. */ public SmileysSelectorBox() { this.setOpaque(false); // Should explicitly remove any border in order to align correctly the // icon. this.setBorder(BorderFactory.createEmptyBorder()); popupMenu = this.getPopupMenu(); popupMenu.setLayout(new GridBagLayout()); popupMenu.setBackground(Color.WHITE); /* * Load the smileys and the UI which represents them on demand because * they are not always necessary. */ popupMenu.addPopupMenuListener(this); this.smiliesService = GuiActivator.getSmiliesReplacementSource(); loadSkin(); } /** * Sets the chat panel, for which smilieys would be created. * * @param chatPanel the chat panel, for which smilieys would be created */ public void setChat(ChatPanel chatPanel) { this.chatPanel = chatPanel; } /** * In order to have a popup which is at the form closest to square. * * @param itemsCount the count of items that will be laid out. * @return the dimensions of the grid */ private Dimension calculateGridDimensions(int itemsCount) { int gridColCount = (int) Math.ceil(Math.sqrt(itemsCount)); /* * FIXME The original code was "(int)Math.ceil(itemsCount/gridRowCount)". * But it was unnecessary because both itemsCount and gridRowCount are * integers and, consequently, itemsCount/gridRowCount gives an integer. * Was the intention to have the division produce a real number? */ int gridRowCount = itemsCount / gridColCount; if (itemsCount > gridRowCount*gridColCount) gridColCount++; return new Dimension(gridColCount, gridRowCount); } /** * Opens the smileys selector box. */ public void open() { this.doClick(); } /** * Returns TRUE if the selector box is opened, otherwise returns FALSE. * * @return TRUE if the selector box is opened, otherwise returns FALSE */ public boolean isMenuSelected() { return isPopupMenuVisible(); } /** * Writes the symbol corresponding to a chosen smiley icon to the write * message area at the end of the current text. * @param e the ActionEvent that notified us */ public void actionPerformed(ActionEvent e) { SmileyMenuItem smileyItem = (SmileyMenuItem) e.getSource(); Smiley smiley = smileyItem.smiley; chatPanel.getChatWritePanel().appendText(smiley.getDefaultString()); chatPanel.getChatWritePanel().getEditorPane().requestFocus(); clearMouseOverEffects(smileyItem); } /** * A custom menu item, which paints round border over selection. */ private static class SmileyMenuItem extends JMenuItem { /** * Class id key used in UIDefaults. */ private static final String uiClassID = SmileyMenuItem.class.getName() + "TreeUI"; /** * Adds the ui class to UIDefaults. */ static { UIManager.getDefaults().put(uiClassID, SIPCommMenuItemUI.class.getName()); } /** * The Smiley depicted by this instance. */ public final Smiley smiley; /** * Initializes a new SmileyMenuItem instance which is to depict * a specific Smiley. * * @param smiley the Smiley to be depicted by the new instance */ public SmileyMenuItem(Smiley smiley) { super(GuiActivator.getResources().getImage(smiley.getImageID())); this.smiley = smiley; } /** * 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; } } /** * Changes the static image of the underlying smiley with a dynamic one. * Also shows the description and smiley string in the description area. * @param e the MouseEvent that notified us */ public void mouseEntered(MouseEvent e) { SmileyMenuItem smileyItem = (SmileyMenuItem) e.getSource(); Smiley smiley = smileyItem.smiley; ImageIcon imageIcon = GuiActivator.getResources().getImage(smiley.getImageID()); smileyItem.setIcon(imageIcon); smileyDescriptionLabel.setText(smiley.getDescription()); smileyTextLabel.setText( "" + smiley.getDefaultString() + ""); } /** * Clears all mouse over effects when the mouse has exited the smiley area. * @param e the MouseEvent that notified us */ public void mouseExited(MouseEvent e) { SmileyMenuItem smileyItem = (SmileyMenuItem) e.getSource(); this.clearMouseOverEffects(smileyItem); } public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} /** * Clears all mouse over effects for the given smiley item. This method * should be invoked when the mouse has exited the smiley area or when * a smiley has been selected and the popup menu is closed. * * @param smileyItem the item for which we clear mouse over effects. */ private void clearMouseOverEffects(SmileyMenuItem smileyItem) { ImageIcon imageIcon = GuiActivator.getResources().getImage(smileyItem.smiley.getImageID()); smileyItem.setIcon(imageIcon); smileyTextLabel.setText(""); smileyDescriptionLabel.setText(""); } /** * Implements PopupMenuListener#popupMenuCanceled(PopupMenuEvent). Does * nothing. * @param e the PopupMenuEvent */ public void popupMenuCanceled(PopupMenuEvent e) {} /** * Implements * PopupMenuListener#popupMenuWillBecomeInvisible(PopupMenuEvent). Does * nothing. * @param e the PopupMenuEvent */ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { // fixes a leak where gif images leak "Image Animator" threads JPopupMenu popupMenu = (JPopupMenu) e.getSource(); for(Component c : popupMenu.getComponents()) { if(c instanceof SmileyMenuItem) { SmileyMenuItem si = (SmileyMenuItem)c; if(si.getIcon() instanceof ImageIcon) { ImageIcon ii = (ImageIcon)si.getIcon(); if(ii != null && ii.getImage() != null) ii.getImage().flush(); } } } } /** * Implements PopupMenuListener#popupMenuWillBecomeVisible(PopupMenuEvent). * Loads the smileys and creates the UI to represent them when they are * first necessary. * @param e the PopupMenuEvent that notified us */ public void popupMenuWillBecomeVisible(PopupMenuEvent e) { JPopupMenu popupMenu = (JPopupMenu) e.getSource(); // Don't populate it again if it's already populated. if (popupMenu.getComponentIndex(smileyTextLabel) != -1) return; Collection imageList = smiliesService.getSmiliesPack(); Dimension gridDimensions = this.calculateGridDimensions(imageList.size()); int gridColCount = gridDimensions.width; int gridRowCount = gridDimensions.height; GridBagConstraints gridBagConstraints = new GridBagConstraints(); int smileyIndex = 0; for (Smiley smiley : imageList) { SmileyMenuItem smileyItem = new SmileyMenuItem(smiley); smileyItem.setPreferredSize(new Dimension(36, 36)); smileyItem.addActionListener(this); smileyItem.addMouseListener(this); gridBagConstraints.anchor = GridBagConstraints.EAST; gridBagConstraints.gridx = smileyIndex % gridColCount; gridBagConstraints.gridy = (int)(Math.floor(smileyIndex / gridColCount)) % gridRowCount; popupMenu.add(smileyItem, gridBagConstraints); smileyIndex++; } smileyDescriptionLabel.setBorder( BorderFactory.createEmptyBorder(0, 5, 0, 0)); gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; gridBagConstraints.gridx = 0; gridBagConstraints.gridy = gridRowCount; gridBagConstraints.gridwidth = gridColCount; popupMenu.add(smileyDescriptionLabel, gridBagConstraints); smileyTextLabel.setBorder( BorderFactory.createEmptyBorder(0, 0, 0, 5)); smileyTextLabel.setHorizontalAlignment(SwingConstants.RIGHT); smileyTextLabel.setPreferredSize(new Dimension(50, 25)); gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; gridBagConstraints.gridx = gridColCount/2; gridBagConstraints.gridy = gridRowCount; popupMenu.add(smileyTextLabel, gridBagConstraints); } /** * Reloads icons in this menu. */ public void loadSkin() { this.setIcon(new ImageIcon(ImageLoader .getImage(ImageLoader.SMILIES_ICON))); if (popupMenu != null) { popupMenu = this.getPopupMenu(); if (smiliesService != null) smiliesService.reloadSmiliesPack(); popupMenu.removeAll(); } } }