/* * 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.plugin.desktoputil.presence.avatar; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.util.*; import javax.swing.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.plugin.desktoputil.presence.avatar.imagepicker.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.ServerStoredDetails.GenericDetail; import net.java.sip.communicator.service.protocol.ServerStoredDetails.ImageDetail; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.account.*; /** * The dialog used as menu. * * @author Damian Minkov */ public class SelectAvatarMenu extends SIPCommPopupMenu implements ActionListener { /** * Logger for this class. */ private static final Logger logger = Logger.getLogger(SelectAvatarMenu.class); /** * Name of choose button. */ private static final String CHOSE_BUTTON_NAME = "chooseButton"; /** * Name of remove button. */ private static final String REMOVE_BUTTON_NAME = "removeButton"; /** * Name of clear button. */ private static final String CLEAR_BUTTON_NAME = "clearButton"; /** * Images shown as history. */ private static final int MAX_STORED_IMAGES = 8; /** * Ordered in columns. */ private static final int IMAGES_PER_COLUMN = 4; /** * Thumbnail width. */ private static final int THUMB_WIDTH = 48; /** * Thumbnail height. */ private static final int THUMB_HEIGHT = 48; /** * Buttons corresponding to images. */ private SIPCommButton recentImagesButtons[] = new SIPCommButton[MAX_STORED_IMAGES]; /** * Next free image index number. */ private int nextImageIndex = 0; /** * The parent button using us. */ private FramedImageWithMenu avatarImage; /** * The AccountID that we want to select avatar for. Could be null if * we want to select a global avatar. */ private AccountID accountID; /** * Creates the dialog. * @param avatarImage the button that will trigger this menu. */ public SelectAvatarMenu(FramedImageWithMenu avatarImage) { this.avatarImage = avatarImage; init(); this.pack(); } public void setAccountID(AccountID accountID) { this.accountID = accountID; } /** * Init visible components. */ private void init() { TransparentPanel panel = new TransparentPanel(new BorderLayout()); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // Title label JLabel titleLabel = new JLabel(DesktopUtilActivator.getResources() .getI18NString("service.gui.avatar.RECENT_ICONS")); titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD)); // fix for displaying text in menu // when using a dark OS theme (as default one in ubuntu) titleLabel.setForeground(new JMenuItem().getForeground()); panel.add(titleLabel, BorderLayout.NORTH); // Init recent images grid TransparentPanel recentImagesGrid = new TransparentPanel(); recentImagesGrid.setLayout(new GridLayout(0, IMAGES_PER_COLUMN)); Dimension thumbsize = new Dimension(THUMB_WIDTH, THUMB_HEIGHT); for (int i=0; i < MAX_STORED_IMAGES; i++) { this.recentImagesButtons[i] = new SIPCommButton(null); this.recentImagesButtons[i].setBorder(BorderFactory.createEtchedBorder()); this.recentImagesButtons[i].setMaximumSize(thumbsize); this.recentImagesButtons[i].setMinimumSize(thumbsize); this.recentImagesButtons[i].setPreferredSize(thumbsize); this.recentImagesButtons[i].addActionListener(this); this.recentImagesButtons[i].setName("" + i); recentImagesGrid.add(this.recentImagesButtons[i]); } panel.add(recentImagesGrid, BorderLayout.CENTER); // Action buttons TransparentPanel buttonsPanel = new TransparentPanel(); buttonsPanel.setLayout(new GridLayout(0, 1)); // we use this menu item just to get its foreground color. Color linkColor = new JMenuItem().getForeground(); addActionButton(buttonsPanel, this, DesktopUtilActivator.getResources().getI18NString( "service.gui.avatar.CHOOSE_ICON"), CHOSE_BUTTON_NAME, linkColor); addActionButton(buttonsPanel, this, DesktopUtilActivator.getResources().getI18NString( "service.gui.avatar.REMOVE_ICON"), REMOVE_BUTTON_NAME, linkColor); addActionButton(buttonsPanel, this, DesktopUtilActivator.getResources().getI18NString( "service.gui.avatar.CLEAR_RECENT"), CLEAR_BUTTON_NAME, linkColor); panel.add(buttonsPanel, BorderLayout.SOUTH); this.setLayout(new BorderLayout()); this.add(panel, BorderLayout.CENTER); } /** * Adds action buttons. * @param buttonsPanel the panel to add to. * @param listener the listener for actions * @param text the text on the button. * @param name name of the button. * @param linkColor the color of the link. */ private static void addActionButton( TransparentPanel buttonsPanel, ActionListener listener, String text, String name, Color linkColor) { SIPCommLinkButton button = new SIPCommLinkButton(text); button.setName(name); button.addActionListener(listener); button.setOpaque(false); button.setLinkColor(linkColor); TransparentPanel panel = new TransparentPanel(new BorderLayout()); panel.add(button, BorderLayout.WEST); buttonsPanel.add(panel); } @Override public void setVisible(boolean b) { refreshRecentImages(); super.setVisible(b); } /** * Refresh images with those stored locally. */ public void refreshRecentImages() { int i; for (i = 0; i < MAX_STORED_IMAGES; i++) { BufferedImage image = AvatarStackManager.loadImage(i); if (image == null) break; this.recentImagesButtons[i].setImage(createThumbnail(image)); this.recentImagesButtons[i].setEnabled(true); } if (i < MAX_STORED_IMAGES) { this.nextImageIndex = i; for (; i < MAX_STORED_IMAGES; i++) { this.recentImagesButtons[i].setImage(null); this.recentImagesButtons[i].setEnabled(false); } } else this.nextImageIndex = MAX_STORED_IMAGES; } /** * Create thumbnail for the image. * @param image to scale. * @return the thumbnail image. */ private static BufferedImage createThumbnail(BufferedImage image) { int width = image.getWidth(); int height = image.getHeight(); // Image smaller than the thumbnail size if (width < THUMB_WIDTH && height < THUMB_HEIGHT) return image; Image i; if (width > height) i = image.getScaledInstance(THUMB_WIDTH, -1, Image.SCALE_SMOOTH); else i = image.getScaledInstance(-1, THUMB_HEIGHT, Image.SCALE_SMOOTH); return ImageUtils.getBufferedImage(i); } /** * Here is all the action. Stores the selected image into protocols and if * needed update it ina AccountStatusPanel. * * @param image the new image. */ private void setNewImage(final BufferedImage image) { // Use separate thread to be sure we don't block UI thread. new Thread() { @Override public void run() { AccountManager accountManager = DesktopUtilActivator.getAccountManager(); for (AccountID accountID : accountManager.getStoredAccounts()) { if (accountManager.isAccountLoaded(accountID)) { ProtocolProviderService protocolProvider = AccountUtils.getRegisteredProviderForAccount( accountID); if(protocolProvider != null && protocolProvider.isRegistered()) { // If account id is set this means that we want to // edit our current account image, not the global // avatar. Hence, we might not want to save this // account image on the server yet. For example: in // the account info plugin the user might set a new // avatar and then click the cancel button. if (SelectAvatarMenu.this.accountID != null) { if (accountID.equals( SelectAvatarMenu.this.accountID)) { OperationSetServerStoredAccountInfo opSet = protocolProvider.getOperationSet( OperationSetServerStoredAccountInfo.class); if (opSet != null) { byte[] imageByte = null; if (image != null) { imageByte = ImageUtils.toByteArray(image); } avatarImage.setImageIcon(imageByte); ImageDetail newDetail = new ImageDetail( "avatar", imageByte); Iterator<GenericDetail> oldDetail = opSet.getDetails(ImageDetail.class); try { if (oldDetail.hasNext()) { opSet.replaceDetail( oldDetail.next(), newDetail); } else opSet.addDetail(newDetail); } catch (Throwable t) { logger.error( "Error setting image", t); } } } } else { OperationSetAvatar opSetAvatar = protocolProvider .getOperationSet(OperationSetAvatar.class); if(opSetAvatar != null) { byte[] imageByte = null; // Sets new avatar if not null. Otherwise, the // opSetAvatar.setAvatar(null) will removes the // current one. if(image != null) { imageByte = ImageUtils.toByteArray(image); } try { opSetAvatar.setAvatar(imageByte); } catch(Throwable t) { logger.error("Error setting image", t); } } } } } } } }.start(); } /** * Clear stored images. */ private void clearRecentImages() { for (int i=0; i < MAX_STORED_IMAGES; i++) { this.recentImagesButtons[i].setImage(null); this.recentImagesButtons[i].setEnabled(false); AvatarStackManager.deleteImage(i); } this.nextImageIndex = 0; } /** * Action performed on various action links(buttons). * * @param e the action. */ public void actionPerformed(ActionEvent e) { JButton src = (JButton) e.getSource(); if (src instanceof SIPCommButton) { // Load image int index = Integer.parseInt(src.getName()); BufferedImage image = AvatarStackManager.loadImage(index); // Set the new image setNewImage(image); } else if (src.getName().equals("chooseButton")) { // Open the image picker Image currentImage = this.avatarImage.getAvatar(); ImagePickerDialog dialog = new ImagePickerDialog(96, 96); byte[] bimage = dialog.showDialog(currentImage); if(bimage == null) return; // New image BufferedImage image = ImageUtils.getBufferedImage( new ImageIcon(bimage).getImage()); // Store image if (this.nextImageIndex == MAX_STORED_IMAGES) { // No more place to store images // Pop the first element (index 0) AvatarStackManager.popFirstImage(MAX_STORED_IMAGES); this.nextImageIndex = MAX_STORED_IMAGES - 1; } // Store the new image on hard drive AvatarStackManager.storeImage(image, this.nextImageIndex); // Inform protocols about the new image setNewImage(image); } else if (src.getName().equals("removeButton")) { // Removes the current photo. setNewImage(null); } else if (src.getName().equals("clearButton")) { clearRecentImages(); } setVisible(false); } }