aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator
diff options
context:
space:
mode:
authorDamian Minkov <damencho@jitsi.org>2010-09-02 15:21:43 +0000
committerDamian Minkov <damencho@jitsi.org>2010-09-02 15:21:43 +0000
commit0423ead8cce878d6e0e34e5ac24b3c07ebca3525 (patch)
tree70e9dfa09c56003c5c587d6e0ab713bd2e7d41d7 /src/net/java/sip/communicator
parentee6ed1fc66f652700394ca09f2d7b06a30c70a30 (diff)
downloadjitsi-0423ead8cce878d6e0e34e5ac24b3c07ebca3525.zip
jitsi-0423ead8cce878d6e0e34e5ac24b3c07ebca3525.tar.gz
jitsi-0423ead8cce878d6e0e34e5ac24b3c07ebca3525.tar.bz2
Integrate choosing and setting avatar image for protocols (currently only for msn and jabber). This work is done as part of Google Summer of Code 2009 by Shashank Tyagi and his mentor Damien Roth.
Diffstat (limited to 'src/net/java/sip/communicator')
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/presence/AccountStatusPanel.java47
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/presence/avatar/AvatarStackManager.java169
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/presence/avatar/SelectAvatarMenu.java353
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/EditPanel.java214
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/ImageClipper.java242
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/ImagePickerDialog.java199
-rw-r--r--src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/WebcamDialog.java311
-rw-r--r--src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java12
-rw-r--r--src/net/java/sip/communicator/impl/protocol/icq/OperationSetAvatarIcqImpl.java26
-rw-r--r--src/net/java/sip/communicator/impl/protocol/icq/OperationSetPersistentPresenceIcqImpl.java6
-rw-r--r--src/net/java/sip/communicator/impl/protocol/icq/OperationSetServerStoredAccountInfoIcqImpl.java38
-rw-r--r--src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java19
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java3
-rw-r--r--src/net/java/sip/communicator/impl/protocol/jabber/OperationSetServerStoredAccountInfoJabberImpl.java95
-rw-r--r--src/net/java/sip/communicator/impl/protocol/msn/ContactGroupMsnImpl.java2
-rw-r--r--src/net/java/sip/communicator/impl/protocol/msn/MsnActivator.java20
-rw-r--r--src/net/java/sip/communicator/impl/protocol/msn/OperationSetAvatarMsnImpl.java26
-rw-r--r--src/net/java/sip/communicator/impl/protocol/msn/OperationSetServerStoredAccountInfoMsnImpl.java553
-rw-r--r--src/net/java/sip/communicator/impl/protocol/msn/ProtocolProviderServiceMsnImpl.java17
-rwxr-xr-xsrc/net/java/sip/communicator/impl/protocol/msn/msn.provider.manifest.mf2
-rw-r--r--src/net/java/sip/communicator/service/protocol/event/ChatRoomMessageReceivedEvent.java24
-rw-r--r--src/net/java/sip/communicator/util/ImageUtils.java48
-rw-r--r--src/net/java/sip/communicator/util/swing/FramedImage.java29
-rw-r--r--src/net/java/sip/communicator/util/swing/FramedImageWithMenu.java274
-rw-r--r--src/net/java/sip/communicator/util/swing/SIPCommLinkButton.java276
-rw-r--r--src/net/java/sip/communicator/util/swing/plaf/SIPCommLinkButtonUI.java75
26 files changed, 3067 insertions, 13 deletions
diff --git a/src/net/java/sip/communicator/impl/gui/main/presence/AccountStatusPanel.java b/src/net/java/sip/communicator/impl/gui/main/presence/AccountStatusPanel.java
index 9451094..c5a38b4 100644
--- a/src/net/java/sip/communicator/impl/gui/main/presence/AccountStatusPanel.java
+++ b/src/net/java/sip/communicator/impl/gui/main/presence/AccountStatusPanel.java
@@ -15,6 +15,7 @@ import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.event.*;
import net.java.sip.communicator.impl.gui.lookandfeel.*;
import net.java.sip.communicator.impl.gui.main.*;
+import net.java.sip.communicator.impl.gui.main.presence.avatar.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.gui.Container;
@@ -31,7 +32,8 @@ import net.java.sip.communicator.util.swing.*;
public class AccountStatusPanel
extends TransparentPanel
implements RegistrationStateChangeListener,
- PluginComponentListener
+ PluginComponentListener,
+ AvatarListener
{
/**
* The desired height of the avatar.
@@ -109,13 +111,16 @@ public class AccountStatusPanel
if (ConfigurationManager.isTransparentWindowEnabled())
this.setUI(new SIPCommOpaquePanelUI());
- accountImageLabel
- = new FramedImage(
+ FramedImageWithMenu imageWithMenu
+ = new FramedImageWithMenu(
+ mainFrame,
new ImageIcon(
ImageLoader
.getImage(ImageLoader.DEFAULT_USER_PHOTO)),
AVATAR_ICON_WIDTH,
AVATAR_ICON_HEIGHT);
+ imageWithMenu.setPopupMenu(new SelectAvatarMenu(imageWithMenu));
+ this.accountImageLabel = imageWithMenu;
accountNameLabel.setFont(
accountNameLabel.getFont().deriveFont(Font.BOLD));
@@ -261,6 +266,19 @@ public class AccountStatusPanel
}
/**
+ * Updates the image that is shown.
+ * @param img the new image.
+ */
+ public void updateImage(ImageIcon img)
+ {
+ accountImageLabel.setImageIcon(img.getImage());
+ accountImageLabel.setMaximumSize(
+ new Dimension(AVATAR_ICON_WIDTH, AVATAR_ICON_HEIGHT));
+ revalidate();
+ repaint();
+ }
+
+ /**
* Starts connecting user interface for the given <tt>protocolProvider</tt>.
* @param protocolProvider the <tt>ProtocolProviderService</tt> to start
* connecting for
@@ -391,6 +409,17 @@ public class AccountStatusPanel
accountNameLabel.setText(accountName);
}
}.start();
+
+ OperationSetAvatar avatarOpSet
+ = protocolProvider.getOperationSet(OperationSetAvatar.class);
+ avatarOpSet.addAvatarListener(this);
+ }
+ else if (evt.getNewState().equals(RegistrationState.UNREGISTERING)
+ || evt.getNewState().equals(RegistrationState.CONNECTION_FAILED))
+ {
+ OperationSetAvatar avatarOpSet
+ = protocolProvider.getOperationSet(OperationSetAvatar.class);
+ avatarOpSet.removeAvatarListener(this);
}
}
@@ -475,4 +504,16 @@ public class AccountStatusPanel
{
return currentImage;
}
+
+ /**
+ * Called whenever a new avatar is defined for one of the protocols that we
+ * have subscribed for.
+ *
+ * @param event the event containing the new image
+ */
+ public void avatarChanged(AvatarEvent event)
+ {
+ currentImage = event.getNewAvatar();
+ accountImageLabel.setImageIcon(currentImage);
+ }
} \ No newline at end of file
diff --git a/src/net/java/sip/communicator/impl/gui/main/presence/avatar/AvatarStackManager.java b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/AvatarStackManager.java
new file mode 100644
index 0000000..4cba88f
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/AvatarStackManager.java
@@ -0,0 +1,169 @@
+/*
+ * SIP Communicator, 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.presence.avatar;
+
+import java.awt.image.*;
+import java.io.*;
+
+import javax.imageio.*;
+
+import net.java.sip.communicator.impl.gui.*;
+
+import net.java.sip.communicator.util.*;
+
+/**
+ * Take cares of storing(deleting, moving) images with the given indexes.
+ */
+public class AvatarStackManager
+{
+ /**
+ * The logger for this class.
+ */
+ private static Logger logger = Logger.getLogger(AvatarStackManager.class);
+
+ /**
+ * The folder where user avatars are stored.
+ */
+ private static final String STORE_DIR = "avatarcache" + File.separator
+ + "userimages" + File.separator;
+
+ /**
+ * Load the image at the defined index from user directory
+ * @param index the image index
+ * @return the image
+ */
+ public static BufferedImage loadImage(int index)
+ {
+ String imagePath;
+ File imageFile;
+
+ try
+ {
+ imagePath = STORE_DIR + index + ".png";
+
+ imageFile = GuiActivator.getFileAccessService()
+ .getPrivatePersistentFile(imagePath);
+ }
+ catch (Exception e)
+ {
+ // Unable to access stored image
+ System.err.println("Unable to access stored image as index " + index);
+ return null;
+ }
+
+ // If the file don't exists, there is no more images to get
+ if (!imageFile.exists())
+ return null;
+
+ try
+ {
+ return ImageIO.read(imageFile);
+ }
+ catch (IOException ioe)
+ {
+ // Error while reading file
+ ioe.printStackTrace(System.err);
+ return null;
+ }
+ }
+
+ /**
+ * Removes the oldest image and as its with lower index.
+ * Moves all indexes. Ant this way we free one index.
+ * @param nbImages
+ */
+ public static void popFirstImage(int nbImages)
+ {
+ for (int i=nbImages-1; i>0; i--)
+ {
+ moveImage(i, i-1);
+ }
+ }
+
+ /**
+ * Moves images.
+ * @param oldIndex the old index.
+ * @param newIndex the new index.
+ */
+ private static void moveImage(int oldIndex, int newIndex)
+ {
+ String oldImagePath = STORE_DIR + oldIndex + ".png";
+ String newImagePath = STORE_DIR + newIndex + ".png";
+
+ try
+ {
+ File oldFile = GuiActivator.getFileAccessService()
+ .getPrivatePersistentFile(oldImagePath);
+
+ File newFile = GuiActivator.getFileAccessService()
+ .getPrivatePersistentFile(newImagePath);
+
+ if (oldFile.exists())
+ oldFile.renameTo(newFile);
+ }
+ catch (Exception e)
+ {
+ // TODO Auto-generated catch block
+ //e.printStackTrace();
+ }
+ }
+
+ /**
+ * Stores an image.
+ * @param image the image
+ * @param index of the image.
+ */
+ public static void storeImage(BufferedImage image, int index)
+ {
+ String imagePath = STORE_DIR + index + ".png";
+
+ try
+ {
+ File storeDir = GuiActivator.getFileAccessService()
+ .getPrivatePersistentDirectory(STORE_DIR);
+
+ // if dir doesn't exist create it
+ storeDir.mkdirs();
+
+ File file = GuiActivator.getFileAccessService()
+ .getPrivatePersistentFile(imagePath);
+
+ ImageIO.write(image, "png", file);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ // Error while writing
+ }
+ catch (Exception e)
+ {
+ // Unable to access image path
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Deletes the stored image.
+ * @param index of the image to delete.
+ */
+ public static void deleteImage(int index)
+ {
+ try
+ {
+ File imageFile = GuiActivator.getFileAccessService()
+ .getPrivatePersistentFile(STORE_DIR + index + ".png");
+
+ if (imageFile.exists() && !imageFile.delete())
+ logger.error("Can't delete stored image at index " + index);
+ }
+ catch (Exception e)
+ {
+ // Can't access file
+ logger.info("Can't access to file: " + STORE_DIR + index + ".png");
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/gui/main/presence/avatar/SelectAvatarMenu.java b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/SelectAvatarMenu.java
new file mode 100644
index 0000000..aeea999
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/SelectAvatarMenu.java
@@ -0,0 +1,353 @@
+/*
+ * SIP Communicator, 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.presence.avatar;
+
+import net.java.sip.communicator.impl.gui.*;
+import net.java.sip.communicator.impl.gui.main.presence.avatar.imagepicker.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.swing.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.util.*;
+
+import javax.swing.*;
+
+/**
+ * The dialog used as menu.
+ *
+ * @author Damian Minkov
+ */
+public class SelectAvatarMenu
+ extends JPopupMenu
+ 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 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;
+
+ /**
+ * Creates the dialog.
+ * @param avatarImage the button that will trigger this menu.
+ */
+ public SelectAvatarMenu(FramedImageWithMenu avatarImage)
+ {
+ this.avatarImage = avatarImage;
+
+ init();
+
+ this.pack();
+ }
+
+ /**
+ * 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(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.RECENT_ICONS"));
+ titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD));
+ 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,
+ GuiActivator.getResources().getI18NString(
+ "service.gui.avatar.CHOOSE_ICON"),
+ CHOSE_BUTTON_NAME,
+ linkColor);
+ addActionButton(buttonsPanel, this,
+ GuiActivator.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(BufferedImage image)
+ {
+ AccountManager accountManager = GuiActivator.getAccountManager();
+
+ for(AccountID accountID : accountManager.getStoredAccounts())
+ {
+ if(accountManager.isAccountLoaded(accountID))
+ {
+ ProtocolProviderService protocolProvider
+ = GuiActivator.getRegisteredProviderForAccount(accountID);
+
+ if(protocolProvider != null)
+ {
+ OperationSetAvatar opSetAvatar
+ = protocolProvider
+ .getOperationSet(OperationSetAvatar.class);
+
+ if(opSetAvatar != null)
+ {
+ try
+ {
+ opSetAvatar.setAvatar(
+ ImageUtils.toByteArray(image));
+ }
+ catch(Throwable t)
+ {
+ logger.error("Error setting image", t);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 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);
+ setVisible(false);
+ }
+ else if (src.getName().equals("chooseButton"))
+ {
+ // Open the image picker
+ Image currentImage = this.avatarImage.getAvatar();
+
+ ImagePickerDialog dialog = new ImagePickerDialog(96, 96);
+
+ setVisible(false);
+
+ 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("clearButton"))
+ {
+ clearRecentImages();
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/EditPanel.java b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/EditPanel.java
new file mode 100644
index 0000000..6eae26e
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/EditPanel.java
@@ -0,0 +1,214 @@
+/*
+ * SIP Communicator, 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.presence.avatar.imagepicker;
+
+import java.awt.*;
+import java.awt.image.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+import net.java.sip.communicator.impl.gui.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.swing.*;
+
+/**
+ * The <tt>EditPanel</tt> manage the image size and the clipper component
+ *
+ * @author Damien Roth
+ * @author Shashank Tyagi
+ */
+public class EditPanel
+ extends TransparentPanel
+ implements ChangeListener
+{
+ private ImageClipper imageClipper;
+ private BufferedImage image;
+ private JSlider imageSizeSlider;
+ private JLabel zoomIn, zoomOut;
+
+ private boolean resizeOnWidth = true;
+ private boolean smallImage = false;
+
+ // Clipping zone dimension
+ private int clippingZoneWidth = 96;
+ private int clippingZoneHeight = 96;
+
+ /**
+ * Create a new <tt>EditPanel</tt>
+ *
+ * @param clippingZoneWidth the width of the clipping zone
+ * @param clippingZoneHeight the height of the clipping zone
+ */
+ public EditPanel(int clippingZoneWidth, int clippingZoneHeight)
+ {
+ super();
+ this.setLayout(new BorderLayout());
+
+ this.clippingZoneWidth = clippingZoneWidth;
+ this.clippingZoneHeight = clippingZoneHeight;
+
+ this.zoomOut = new JLabel(GuiActivator.getResources()
+ .getImage("service.gui.buttons.ZOOM_OUT"));
+
+ this.zoomIn = new JLabel(GuiActivator.getResources()
+ .getImage("service.gui.buttons.ZOOM_IN"));
+
+ imageSizeSlider = new JSlider(clippingZoneWidth, clippingZoneWidth,
+ clippingZoneWidth);
+ imageSizeSlider.addChangeListener(this);
+ imageSizeSlider.setOpaque(false);
+ imageSizeSlider.setToolTipText(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.IMAGE_SIZE"));
+
+ TransparentPanel sliderPanel = new TransparentPanel();
+ sliderPanel.add(this.zoomOut);
+ sliderPanel.add(this.imageSizeSlider);
+ sliderPanel.add(this.zoomIn);
+
+ this.imageClipper = new ImageClipper(this.clippingZoneWidth,
+ this.clippingZoneHeight);
+
+ this.add(imageClipper, BorderLayout.CENTER);
+ this.add(sliderPanel, BorderLayout.SOUTH);
+ }
+
+ /**
+ * Sets the image to be edited
+ *
+ * @param image the image to be edited
+ */
+ public void setImage(BufferedImage image)
+ {
+ int width = image.getWidth(null);
+ int height = image.getHeight(null);
+
+
+ // Checks if one dimension of the image is smaller than the clipping zone
+ if (width < this.clippingZoneWidth || height < this.clippingZoneHeight)
+ {
+ // Disable the slider used to set the size of the image
+ this.enableSlider(false);
+ this.smallImage = true;
+
+
+ /* Resize the image to match the clipping zone
+ * First case :
+ * * Image wider than high and the clipping zone
+ * * Image wider than high and included in the clipping zone
+ * Second case :
+ * * Image higher than wide and the clipping zone
+ * * Image wider than high and included in the clipping zone
+ */
+ if ((width > height && width > this.clippingZoneWidth)
+ || (height > width && height <= this.clippingZoneHeight))
+ {
+ this.image = ImageUtils.getBufferedImage(
+ image.getScaledInstance(
+ -1, this.clippingZoneHeight, Image.SCALE_SMOOTH));
+ }
+ else
+ {
+ this.image = ImageUtils.getBufferedImage(
+ image.getScaledInstance(
+ this.clippingZoneWidth, -1, Image.SCALE_SMOOTH));
+ }
+ }
+ else
+ {
+ this.image = image;
+ this.enableSlider(true);
+ this.resizeOnWidth = !(height < width);
+ this.imageSizeSlider.setMaximum(Math.min(width, height));
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run()
+ {
+ reset();
+ }
+ });
+ }
+
+ /**
+ * Reset the editor
+ */
+ public void reset()
+ {
+ imageSizeSlider.setValue(this.imageSizeSlider.getMinimum());
+ drawImage();
+ }
+
+ /**
+ * Returns the clipped image.
+ *
+ * @return
+ */
+ public byte[] getClippedImage()
+ {
+ BufferedImage fullImage = getResizedImage(true);
+
+
+ Rectangle clipping = this.imageClipper.getClipping();
+
+ BufferedImage subImage = fullImage.getSubimage(clipping.x, clipping.y,
+ clipping.width, clipping.height);
+
+ byte[] result = ImageUtils.toByteArray(subImage);
+
+ return result;
+ }
+
+ /**
+ * Resize the image.
+ * @param hq
+ * @return
+ */
+ private BufferedImage getResizedImage(boolean hq)
+ {
+ BufferedImage i = null;
+ int size = this.imageSizeSlider.getValue();
+
+ if (this.resizeOnWidth)
+ {
+ i = ImageUtils.getBufferedImage(
+ this.image.getScaledInstance(
+ size,
+ -1,
+ (hq) ? Image.SCALE_SMOOTH : Image.SCALE_FAST));
+ }
+ else
+ {
+ i = ImageUtils.getBufferedImage(
+ this.image.getScaledInstance(
+ -1,
+ size,
+ (hq) ? Image.SCALE_SMOOTH : Image.SCALE_FAST));
+ }
+
+ return i;
+ }
+
+ private void drawImage()
+ {
+ // Use high quality scalling when the image is smaller than the clipper
+ this.imageClipper.setImage(getResizedImage(smallImage));
+ }
+
+ private void enableSlider(boolean enabled)
+ {
+ this.imageSizeSlider.setEnabled(enabled);
+ this.zoomIn.setEnabled(enabled);
+ this.zoomOut.setEnabled(enabled);
+ }
+
+ public void stateChanged(ChangeEvent e)
+ {
+ // New size selected update the clipper
+ drawImage();
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/ImageClipper.java b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/ImageClipper.java
new file mode 100644
index 0000000..a25b82c
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/ImageClipper.java
@@ -0,0 +1,242 @@
+/*
+ * SIP Communicator, 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.presence.avatar.imagepicker;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+
+import javax.swing.*;
+
+import net.java.sip.communicator.util.swing.*;
+
+/**
+ * Component allowing the user to easily clip an image
+ *
+ * @author Damien Roth
+ */
+public class ImageClipper
+ extends JComponent
+ implements MouseListener, MouseMotionListener
+{
+ private static final int CLIP_PANEL_WIDTH = 320;
+ private static final int CLIP_PANEL_HEIGHT = 240;
+
+ private static final Color IMAGE_BORDER_COLOR
+ = new Color(174, 189, 215);
+ private static final Color IMAGE_OVERLAY_COLOR
+ = new Color(1.0f, 1.0f, 1.0f, 0.4f);
+
+ private BufferedImage image = null;
+ private Rectangle imageRect;
+ private Point imageBottomRight;
+
+ private Rectangle clippingZoneRect;
+ private Point clippingZoneBottomRight;
+
+ // Mouse drag vars
+ private int mouseStartX;
+ private int mouseStartY;
+ private int xInit;
+ private int yInit;
+
+ /**
+ * Construct an new image clipper
+ *
+ * @param cropZoneWidth the width of the clip zone
+ * @param cropZoneHeight the height of the clip zone
+ */
+ public ImageClipper(int cropZoneWidth, int cropZoneHeight)
+ {
+ Dimension d = new Dimension(CLIP_PANEL_WIDTH, CLIP_PANEL_HEIGHT);
+
+ this.setSize(d);
+ this.setMaximumSize(d);
+ this.setMinimumSize(d);
+ this.setPreferredSize(d);
+
+ this.initClippingZone(cropZoneWidth, cropZoneHeight);
+ this.imageRect = new Rectangle(this.clippingZoneRect.getLocation());
+ this.imageBottomRight = new Point(0,0);
+
+ this.addMouseListener(this);
+ this.addMouseMotionListener(this);
+ }
+
+ /**
+ * Compute static values of the clipping zone
+ *
+ * @param width the width of the clipping zone
+ * @param height the height of the clipping zone
+ */
+ private void initClippingZone(int width, int height)
+ {
+ this.clippingZoneRect = new Rectangle(width, height);
+ this.clippingZoneRect.x = (CLIP_PANEL_WIDTH / 2) - (width / 2);
+ this.clippingZoneRect.y = (CLIP_PANEL_HEIGHT / 2) - (height / 2);
+
+ this.clippingZoneBottomRight = new Point(
+ this.clippingZoneRect.x + this.clippingZoneRect.width,
+ this.clippingZoneRect.y + this.clippingZoneRect.height
+ );
+ }
+
+ /**
+ * Defines the image to be clipped
+ *
+ * @param image the image to be clipped
+ */
+ public void setImage(BufferedImage image)
+ {
+ boolean updated = false;
+ this.image = image;
+
+ this.imageRect.width = image.getWidth(this);
+ this.imageRect.height = image.getHeight(this);
+
+ this.imageBottomRight.x = this.imageRect.x + this.imageRect.width;
+ this.imageBottomRight.y = this.imageRect.y + this.imageRect.height;
+
+ if (this.imageBottomRight.x < this.clippingZoneBottomRight.x)
+ {
+ this.imageRect.x +=
+ this.clippingZoneBottomRight.x - this.imageBottomRight.x;
+ updated = true;
+ }
+ if (this.imageBottomRight.y < this.clippingZoneBottomRight.y)
+ {
+ this.imageRect.y +=
+ this.clippingZoneBottomRight.y - this.imageBottomRight.y;
+ updated = true;
+ }
+
+ if (updated)
+ {
+ this.imageBottomRight.x = this.imageRect.x + this.imageRect.width;
+ this.imageBottomRight.y = this.imageRect.y + this.imageRect.height;
+ }
+
+ this.repaint();
+ }
+
+ /**
+ * Returns the clipped area of the image
+ *
+ * @return the clipped area
+ */
+ public Rectangle getClipping()
+ {
+ Rectangle clipping = new Rectangle();
+
+ clipping.setSize(this.clippingZoneRect.getSize());
+ clipping.x = this.clippingZoneRect.x - this.imageRect.x;
+ clipping.y = this.clippingZoneRect.y - this.imageRect.y;
+
+ return clipping;
+ }
+
+ @Override
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+
+ g = g.create();
+ AntialiasingManager.activateAntialiasing(g);
+
+ // Draw image
+ g.drawImage(this.image, this.imageRect.x, this.imageRect.y,
+ this.imageRect.width, this.imageRect.height, this);
+
+ // Image overlay
+ drawImageOverlay(g);
+
+ // Image border
+ g.setColor(ImageClipper.IMAGE_BORDER_COLOR);
+ g.drawRoundRect(this.imageRect.x-2, this.imageRect.y-2,
+ this.imageRect.width+3, this.imageRect.height+3, 2, 2);
+ g.drawRoundRect(this.imageRect.x-1, this.imageRect.y-1,
+ this.imageRect.width+1, this.imageRect.height+1, 2, 2);
+
+ // Select rect
+ g.setColor(Color.BLACK);
+ g.drawRect(this.clippingZoneRect.x, this.clippingZoneRect.y,
+ this.clippingZoneRect.width, this.clippingZoneRect.height);
+ }
+
+ /**
+ * Draw an overlay over the parts of the images which are not in the clip zone
+ *
+ * @param g the Graphics used to draw
+ */
+ private void drawImageOverlay(Graphics g)
+ {
+ int width, height;
+
+ g.setColor(ImageClipper.IMAGE_OVERLAY_COLOR);
+
+ width = this.clippingZoneRect.x - this.imageRect.x;
+ if (width > 0)
+ g.fillRect(this.imageRect.x, this.imageRect.y,
+ width, this.imageRect.height);
+
+ width = this.imageRect.x + this.imageRect.width
+ - this.clippingZoneBottomRight.x;
+ if (width > 0)
+ g.fillRect(this.clippingZoneBottomRight.x, this.imageRect.y,
+ width, this.imageRect.height);
+
+ // Top
+ height = this.clippingZoneRect.y - this.imageRect.y;
+ if (height > 0)
+ g.fillRect(this.clippingZoneRect.x, this.imageRect.y,
+ this.clippingZoneRect.width, height);
+
+ // Bottom
+ height = (this.imageRect.y + this.imageRect.height)
+ - (this.clippingZoneBottomRight.y);
+ if (height > 0)
+ g.fillRect(this.clippingZoneRect.x, this.clippingZoneBottomRight.y,
+ this.clippingZoneRect.width, height);
+ }
+
+ public void mousePressed(MouseEvent e)
+ {
+ // Init the dragging
+ mouseStartX = e.getX();
+ mouseStartY = e.getY();
+ xInit = this.imageRect.x;
+ yInit = this.imageRect.y;
+ }
+
+ public void mouseDragged(MouseEvent e)
+ {
+ // New position of the image
+ int xpos = xInit + (e.getX() - mouseStartX);
+ int ypos = yInit + (e.getY() - mouseStartY);
+
+ // Checks if the image doesn't go out of the clip zone
+ if (xpos <= this.clippingZoneRect.x && xpos
+ + this.imageRect.width > this.clippingZoneBottomRight.x)
+ this.imageRect.x = xpos;
+
+ if (ypos <= this.clippingZoneRect.y && ypos
+ + this.imageRect.height > this.clippingZoneBottomRight.y)
+ this.imageRect.y = ypos;
+
+ this.repaint();
+ }
+
+ public void mouseClicked(MouseEvent e) {}
+
+ public void mouseEntered(MouseEvent e) {}
+
+ public void mouseExited(MouseEvent e) {}
+
+ public void mouseReleased(MouseEvent e) {}
+
+ public void mouseMoved(MouseEvent e) {}
+}
diff --git a/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/ImagePickerDialog.java b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/ImagePickerDialog.java
new file mode 100644
index 0000000..2a1eb05
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/ImagePickerDialog.java
@@ -0,0 +1,199 @@
+package net.java.sip.communicator.impl.gui.main.presence.avatar.imagepicker;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.io.*;
+
+import javax.imageio.*;
+import javax.swing.*;
+
+import net.java.sip.communicator.impl.gui.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.swing.*;
+
+public class ImagePickerDialog
+ extends SIPCommDialog
+ implements ActionListener
+{
+ private TransparentPanel fullEditPanel;
+
+ private EditPanel editPanel;
+
+ private JButton okButton, cancelButton;
+ private JButton resetButton, selectFileButton, webcamButton;
+
+ private boolean editCanceled = false;
+
+ public ImagePickerDialog(int clipperZoneWidth, int clipperZoneHeight)
+ {
+ super();
+ this.initComponents(clipperZoneWidth, clipperZoneHeight);
+ this.initDialog();
+
+ this.pack();
+ this.setLocationRelativeTo(null);
+ }
+
+ private void initDialog()
+ {
+ this.setTitle(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.IMAGE_PICKER"));
+ this.setModal(true);
+ this.setResizable(true);
+
+
+ this.setLayout(new BorderLayout());
+
+ TransparentPanel editButtonsPanel = new TransparentPanel();
+ editButtonsPanel.setLayout(new GridLayout(1, 3, 5, 0));
+ editButtonsPanel.add(this.selectFileButton);
+ editButtonsPanel.add(this.webcamButton);
+ editButtonsPanel.add(this.resetButton);
+
+ this.fullEditPanel = new TransparentPanel();
+ this.fullEditPanel.setLayout(new BorderLayout());
+ this.fullEditPanel.add(this.editPanel, BorderLayout.CENTER);
+ this.fullEditPanel.add(editButtonsPanel, BorderLayout.SOUTH);
+
+ TransparentPanel okCancelPanel = new TransparentPanel();
+ okCancelPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ okCancelPanel.add(cancelButton);
+ okCancelPanel.add(okButton);
+
+ this.add(fullEditPanel, BorderLayout.CENTER);
+ this.add(okCancelPanel, BorderLayout.SOUTH);
+
+ this.pack();
+
+ }
+
+ private void initComponents(int clipperZoneWidth, int clipperZoneHeight)
+ {
+ // Edit panel
+ this.editPanel = new EditPanel(clipperZoneWidth, clipperZoneHeight);
+
+ // Buttons
+ this.okButton = new JButton(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.SET"));
+ this.okButton.addActionListener(this);
+ this.okButton.setName("okButton");
+
+ this.cancelButton = new JButton(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.CANCEL"));
+ this.cancelButton.addActionListener(this);
+ this.cancelButton.setName("cancelButton");
+
+ this.resetButton = new JButton(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.RESET"));
+ this.resetButton.addActionListener(this);
+ this.resetButton.setName("resetButton");
+
+ this.selectFileButton = new JButton(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.CHOOSE_FILE"));
+ this.selectFileButton.addActionListener(this);
+ this.selectFileButton.setName("selectFileButton");
+
+ this.webcamButton = new JButton(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.TAKE_PHOTO"));
+ // disable it till we support it
+ this.webcamButton.setEnabled(false);
+
+ this.webcamButton.addActionListener(this);
+ this.webcamButton.setName("webcamButton");
+ }
+
+ public byte[] showDialog(Image image)
+ {
+ if(image != null)
+ this.editPanel.setImage(ImageUtils.getBufferedImage(image));
+
+ this.setVisible(true);
+
+ if (this.editCanceled)
+ return null;
+ else
+ return this.editPanel.getClippedImage();
+ }
+
+ public void actionPerformed(ActionEvent e)
+ {
+ String name = ((JButton) e.getSource()).getName();
+ if (name.equals("cancelButton"))
+ {
+ editCanceled = true;
+ this.setVisible(false);
+ }
+ else if (name.equals("selectFileButton"))
+ {
+ SipCommFileChooser chooser = GenericFileDialog.create(
+ GuiActivator.getUIService().getMainFrame(),
+ GuiActivator.getResources().getI18NString(
+ "service.gui.avatar.imagepicker.CHOOSE_FILE"),
+ SipCommFileChooser.LOAD_FILE_OPERATION);
+
+ chooser.addFilter(new ImageFileFilter());
+
+ File selectedFile = chooser.getFileFromDialog();
+ if(selectedFile != null)
+ {
+ try
+ {
+ BufferedImage image = ImageIO.read(selectedFile);
+ this.editPanel.setImage(image);
+ }
+ catch (IOException ioe)
+ {
+ // TODO Auto-generated catch block
+ ioe.printStackTrace();
+ }
+ }
+ }
+ else if (name.equals("okButton"))
+ {
+ editCanceled = false;
+ this.setVisible(false);
+ }
+ else if (name.equals("resetButton"))
+ {
+ this.editPanel.reset();
+ }
+ else
+ {
+// WebcamDialog dialog = new WebcamDialog(this);
+// dialog.setVisible(true);
+// byte[] bimage = dialog.getGrabbedImage();
+//
+// if (bimage != null)
+// {
+// Image i = new ImageIcon(bimage).getImage();
+// editPanel.setImage(ImageUtils.getBufferedImage(i));
+// }
+ }
+ }
+
+ protected void close(boolean isEscaped)
+ {
+ dispose();
+ }
+
+ class ImageFileFilter extends SipCommFileFilter
+ {
+ public boolean accept(File f)
+ {
+ String path = f.getAbsolutePath().toLowerCase();
+ if (path.matches("(.*)\\.(jpg|jpeg|png|bmp)$") ||
+ f.isDirectory())
+ return true;
+
+ else
+ return false;
+ }
+
+ public String getDescription()
+ {
+ return GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.IMAGE_FILES");
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/WebcamDialog.java b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/WebcamDialog.java
new file mode 100644
index 0000000..ede148e
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/gui/main/presence/avatar/imagepicker/WebcamDialog.java
@@ -0,0 +1,311 @@
+/*
+ * SIP Communicator, 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.presence.avatar.imagepicker;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+import net.java.sip.communicator.impl.gui.*;
+import net.java.sip.communicator.service.audionotifier.*;
+//import net.java.sip.communicator.service.media.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.swing.*;
+
+/**
+ * A dialog showing the webcam and allowing the user to grap a snapshot
+ *
+ * @author Damien Roth
+ */
+public class WebcamDialog
+ extends SIPCommDialog
+ implements ActionListener
+// , VideoListener
+{
+ private VideoContainer videoContainer;
+ private Component videoComponent = null;
+
+ private JButton cancelButton;
+ private JButton grabSnapshot;
+
+ private byte[] grabbedImage = null;
+
+ private TransparentPanel southPanel;
+
+ private TransparentPanel timerPanel;
+ private TimerImage[] timerImages = new TimerImage[3];
+
+ /**
+ * Construct a <tt>WebcamDialog</tt>
+ * @param parent the ImagePickerDialog
+ */
+ public WebcamDialog(ImagePickerDialog parent)
+ {
+ super(parent);
+ this.setTitle(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.TAKE_PHOTO"));
+ this.setModal(true);
+
+ init();
+ }
+
+ /**
+ * Init the dialog
+ */
+ private void init()
+ {
+ this.grabSnapshot = new JButton();
+ this.grabSnapshot.setText(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.CLICK"));
+ this.grabSnapshot.setName("grab");
+ this.grabSnapshot.addActionListener(this);
+ this.grabSnapshot.setEnabled(false);
+
+ this.cancelButton = new JButton(GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.CANCEL"));
+ this.cancelButton.setName("cancel");
+ this.cancelButton.addActionListener(this);
+
+ initAccessWebcam();
+
+
+ // Timer Panel
+ this.timerPanel = new TransparentPanel();
+ this.timerPanel.setLayout(new GridLayout(0, 3));
+
+ TransparentPanel tp;
+ for (int i = 0; i < this.timerImages.length; i++)
+ {
+ this.timerImages[i] = new TimerImage("" + (3-i));
+
+ tp = new TransparentPanel();
+ tp.add(this.timerImages[i], BorderLayout.CENTER);
+
+ this.timerPanel.add(tp);
+ }
+
+ TransparentPanel buttonsPanel = new TransparentPanel(new GridLayout(1, 2));
+ buttonsPanel.add(this.grabSnapshot);
+ buttonsPanel.add(this.cancelButton);
+
+ // South Panel
+ this.southPanel = new TransparentPanel(new BorderLayout());
+ this.southPanel.add(this.timerPanel, BorderLayout.CENTER);
+ this.southPanel.add(buttonsPanel, BorderLayout.SOUTH);
+
+ this.add(this.videoContainer, BorderLayout.CENTER);
+ this.add(this.southPanel, BorderLayout.SOUTH);
+ }
+
+ /**
+ * Init the access to the webcam (asynchonous call)
+ */
+ private void initAccessWebcam()
+ {
+ Dimension d = new Dimension(320, 240);
+
+ // Create a container for the video
+ this.videoContainer = new VideoContainer(new JLabel(
+ GuiActivator.getResources()
+ .getI18NString("service.gui.avatar.imagepicker.INITIALIZING"),
+ JLabel.CENTER));
+ this.videoContainer.setPreferredSize(d);
+ this.videoContainer.setMinimumSize(d);
+ this.videoContainer.setMaximumSize(d);
+
+// try
+// {
+// // Call the method in the media service
+// GuiActivator.getMediaService().createLocalVideoComponent(this);
+// } catch (MediaException e)
+// {
+// //todo: In what scenarios are exceptions thrown and how to manage them?
+// this.videoContainer = new VideoContainer(new JLabel(
+// GuiActivator.getResources()
+// .getI18NString("service.gui.avatar.imagepicker.WEBCAM_ERROR")));
+// e.printStackTrace();
+// }
+ }
+
+ /**
+ * Grap the current image of the webcam throught the MediaService
+ */
+ private void grabSnapshot()
+ {
+ // Just call the method "grabSnapshot" from the MediaService with the component
+// try
+// {
+// this.grabbedImage = GuiActivator.getMediaService()
+// .grabSnapshot(this.videoComponent);
+// }
+// catch (Exception e)
+// {
+// e.printStackTrace();
+// }
+ close(false);
+ this.setVisible(false);
+ }
+
+ /**
+ * Return the grabbed snapshot as a byte array
+ *
+ * @return the grabbed snapshot
+ */
+ public byte[] getGrabbedImage()
+ {
+ return this.grabbedImage;
+ }
+
+ /**
+ * Play a snapshot sound
+ */
+ private void playSound()
+ {
+ String soundKey = GuiActivator.getResources()
+ .getSoundPath("WEBCAM_SNAPSHOT");
+
+ SCAudioClip audio = GuiActivator.getAudioNotifier()
+ .createAudio(soundKey);
+
+ audio.play();
+ }
+
+ protected void close(boolean isEscaped)
+ {
+// try
+// {
+// if (this.videoComponent != null)
+// {
+// GuiActivator.getMediaService()
+// .disposeLocalVideoComponent(this.videoComponent);
+// this.videoComponent = null;
+// }
+// }
+// catch (MediaException e)
+// {
+// // Better manager the exception !
+// e.printStackTrace();
+// }
+ }
+
+ public void videoAdded(VideoEvent event)
+ {
+ // Here is the important part. With this event, you get the component
+ // containing the video !
+
+ // You need to keep it. The returned componant is the key for all
+ // the other methods !
+ this.videoComponent = event.getVisualComponent();
+
+ // Add the component in the container
+ this.videoContainer.add(this.videoComponent);
+
+ this.grabSnapshot.setEnabled(true);
+ }
+
+ public void videoRemoved(VideoEvent event)
+ {
+ // In case of the video is removed elsewhere
+ this.videoComponent = null;
+ }
+
+ public void actionPerformed(ActionEvent e)
+ {
+ String actName = ((JButton) e.getSource()).getName();
+
+ if (actName.equals("grab"))
+ {
+ if (this.videoComponent == null)
+ return;
+
+ this.grabSnapshot.setEnabled(false);
+ new SnapshotTimer().start();
+ }
+ else
+ {
+ close(false);
+ this.setVisible(false);
+ }
+ }
+
+ private class SnapshotTimer
+ extends Thread
+ {
+ @Override
+ public void run()
+ {
+ int i;
+
+ for (i=0; i < timerImages.length; i++)
+ {
+ timerImages[i].setElapsed();
+ try
+ {
+ Thread.sleep(1000);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ playSound();
+ grabSnapshot();
+
+ WebcamDialog.this.setVisible(false);
+ WebcamDialog.this.dispose();
+
+ }
+ }
+
+ private class TimerImage
+ extends JComponent
+ {
+ private static final int WIDTH = 30;
+ private static final int HEIGHT = 30;
+
+ private boolean isElapsed = false;
+ private Font textFont = null;
+ private String second;
+
+ public TimerImage(String second)
+ {
+ Dimension d = new Dimension(WIDTH, HEIGHT);
+ this.setPreferredSize(d);
+ this.setMinimumSize(d);
+
+
+ this.textFont = new Font("Sans", Font.BOLD, 20);
+ this.second = second;
+ }
+
+ public void setElapsed()
+ {
+ this.isElapsed = true;
+ this.repaint();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g)
+ {
+ Graphics2D g2d = (Graphics2D) g;
+ AntialiasingManager.activateAntialiasing(g);
+
+ Color c = (isElapsed)
+ ? Color.RED
+ : new Color(150, 0, 0);
+
+ g2d.setColor(c);
+ g2d.fillOval(0, 0, WIDTH, HEIGHT);
+
+ g2d.setColor(Color.WHITE);
+ g2d.setFont(textFont);
+ g2d.drawString(this.second, 7, 21);
+ }
+ }
+}
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 d9bd24c..0842e50 100644
--- a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java
+++ b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java
@@ -818,6 +818,18 @@ public class ImageLoader
public static final ImageID CHAT_ROOM_CONFIG
= new ImageID("service.gui.buttons.CHAT_ROOM_CONFIG");
+ /**
+ * Zoom out Image for avatar panel
+ */
+ public static final ImageID MAGNIFIER_ZOOM_OUT
+ = new ImageID("service.gui.buttons.ZOOM_OUT");
+
+ /**
+ * Zoom in Image for avatar panel
+ */
+ public static final ImageID MAGNIFIER_ZOOM_IN
+ = new ImageID("service.gui.buttons.ZOOM_IN");
+
/*
* =======================================================================
* ------------------------ EDIT TOOLBAR ICONS ---------------------------
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/OperationSetAvatarIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/OperationSetAvatarIcqImpl.java
new file mode 100644
index 0000000..068fd35
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/OperationSetAvatarIcqImpl.java
@@ -0,0 +1,26 @@
+/*
+ * SIP Communicator, 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.protocol.icq;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * A simple implementation of the <tt>OperationSetAvatar</tt> interface for the
+ * icq protocol.
+ *
+ * @author Damian Minkov
+ */
+public class OperationSetAvatarIcqImpl
+ extends AbstractOperationSetAvatar<ProtocolProviderServiceIcqImpl>
+{
+ public OperationSetAvatarIcqImpl(
+ ProtocolProviderServiceIcqImpl parentProvider,
+ OperationSetServerStoredAccountInfo accountInfoOpSet)
+ {
+ super(parentProvider, accountInfoOpSet, 0, 0, 0);
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/OperationSetPersistentPresenceIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/OperationSetPersistentPresenceIcqImpl.java
index 1b9fcc2..eef5815 100644
--- a/src/net/java/sip/communicator/impl/protocol/icq/OperationSetPersistentPresenceIcqImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/icq/OperationSetPersistentPresenceIcqImpl.java
@@ -1098,6 +1098,12 @@ public class OperationSetPersistentPresenceIcqImpl
|| evt.getNewState() == RegistrationState.AUTHENTICATION_FAILED
|| evt.getNewState() == RegistrationState.CONNECTION_FAILED)
{
+ if(presenceQueryTimer != null)
+ {
+ presenceQueryTimer.cancel();
+ presenceQueryTimer = null;
+ }
+
//since we are disconnected, we won't receive any further status
//updates so we need to change by ourselves our own status as
//well as set to offline all contacts in our contact list that
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/OperationSetServerStoredAccountInfoIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/OperationSetServerStoredAccountInfoIcqImpl.java
index cfc304d..b30e130 100644
--- a/src/net/java/sip/communicator/impl/protocol/icq/OperationSetServerStoredAccountInfoIcqImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/icq/OperationSetServerStoredAccountInfoIcqImpl.java
@@ -275,6 +275,24 @@ public class OperationSetServerStoredAccountInfoIcqImpl
throw new ArrayIndexOutOfBoundsException(
"Max count for this detail is already reached");
+ if (detail instanceof ImageDetail)
+ {
+ if (iconListener == null)
+ {
+ iconListener = new IconUpdateListener();
+
+ this.icqProvider.getAimConnection().getExternalServiceManager().
+ getIconServiceArbiter().addIconRequestListener(
+ new IconUpdateListener());
+ }
+
+ icqProvider.getAimConnection().getMyBuddyIconManager().requestSetIcon(
+ ByteBlock.wrap(((ServerStoredDetails.ImageDetail) detail).getBytes()));
+ infoRetreiver.detailsChanged(uin);
+
+ return;
+ }
+
// everything is ok , so set it
alreadySetDetails.add(detail);
@@ -562,6 +580,26 @@ public class OperationSetServerStoredAccountInfoIcqImpl
if(!isFound)
return false;
+ //replacing in case of image
+ if (newDetailValue instanceof ImageDetail)
+ {
+ if (iconListener == null)
+ {
+ iconListener = new IconUpdateListener();
+
+ this.icqProvider.getAimConnection().getExternalServiceManager().
+ getIconServiceArbiter().addIconRequestListener(
+ new IconUpdateListener());
+ }
+ icqProvider.getAimConnection().getMyBuddyIconManager()
+ .requestSetIcon(ByteBlock.wrap(
+ ((ServerStoredDetails.ImageDetail) newDetailValue)
+ .getBytes()));
+
+ infoRetreiver.detailsChanged(uin);
+ return true;
+ }
+
SuccessResponseListener responseListener = new SuccessResponseListener();
// // if toBeCleared == null. make it empty one
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java
index c896864..3cbe5da 100644
--- a/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java
@@ -497,12 +497,23 @@ public class ProtocolProviderServiceIcqImpl
infoRetreiver,
this));
- addSupportedOperationSet(
- OperationSetServerStoredAccountInfo.class,
- new OperationSetServerStoredAccountInfoIcqImpl(
+ OperationSetServerStoredAccountInfoIcqImpl
+ serverStoredAccountInfoOpSet =
+ new OperationSetServerStoredAccountInfoIcqImpl(
infoRetreiver,
screenname,
- this));
+ this);
+ addSupportedOperationSet(
+ OperationSetServerStoredAccountInfo.class,
+ serverStoredAccountInfoOpSet);
+
+// Currently disabled as when we send avatar
+// we receive an error from server
+// addSupportedOperationSet(
+// OperationSetAvatar.class,
+// new OperationSetAvatarIcqImpl(
+// this,
+// serverStoredAccountInfoOpSet));
addSupportedOperationSet(
OperationSetWebAccountRegistration.class,
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java
index 280fffb..6bda38d 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java
@@ -1626,6 +1626,9 @@ public class ChatRoomJabberImpl
newMessage,
messageReceivedEventType);
+ if(delay != null)
+ msgReceivedEvt.setHistoryMessage(true);
+
fireMessageEvent(msgReceivedEvt);
}
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetServerStoredAccountInfoJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetServerStoredAccountInfoJabberImpl.java
index e6e9d35..5998c20 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetServerStoredAccountInfoJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetServerStoredAccountInfoJabberImpl.java
@@ -6,9 +6,11 @@
*/
package net.java.sip.communicator.impl.protocol.jabber;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Vector;
+import java.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smackx.packet.*;
+
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.ServerStoredDetails.*;
@@ -182,7 +184,45 @@ public class OperationSetServerStoredAccountInfoJabberImpl
OperationFailedException,
ArrayIndexOutOfBoundsException
{
+ assertConnected();
+
+ /*
+ Currently as the function only provided the list of classes that
+ currently have data associated with them
+ in Jabber InfoRetreiver we have to skip this check*/
+ //if (!isDetailClassSupported(detail.getClass())) {
+ // throw new IllegalArgumentException(
+ // "implementation does not support such details " +
+ // detail.getClass());
+ //}
+
+ Iterator iter = getDetails(detail.getClass());
+ int currentDetailsSize = 0;
+ while (iter.hasNext()) {
+ currentDetailsSize++;
+ }
+ if (currentDetailsSize >= getMaxDetailInstances(detail.getClass()))
+ {
+ throw new ArrayIndexOutOfBoundsException(
+ "Max count for this detail is already reached");
+ }
+
+ if(detail instanceof ImageDetail)
+ {
+ try
+ {
+ VCard v1 = new VCard();
+ v1.load(jabberProvider.getConnection());
+
+ v1.setAvatar(((ImageDetail) detail).getBytes());
+
+ v1.save(jabberProvider.getConnection());
+ } catch (XMPPException xmppe)
+ {
+ xmppe.printStackTrace();
+ }
+ }
}
/**
@@ -221,6 +261,55 @@ public class OperationSetServerStoredAccountInfoJabberImpl
ServerStoredDetails.GenericDetail newDetailValue)
throws ClassCastException, OperationFailedException
{
+ assertConnected();
+
+ if (!newDetailValue.getClass().equals(currentDetailValue.getClass()))
+ {
+ throw new ClassCastException(
+ "New value to be replaced is not as the current one");
+ }
+ // if values are the same no change
+ if (currentDetailValue.equals(newDetailValue))
+ {
+ return true;
+ }
+
+ boolean isFound = false;
+ Iterator iter =
+ infoRetreiver.getDetails(uin, currentDetailValue.getClass());
+
+ while (iter.hasNext())
+ {
+ GenericDetail item = (GenericDetail) iter.next();
+ if (item.equals(currentDetailValue))
+ {
+ isFound = true;
+ break;
+ }
+ }
+ // current detail value does not exist
+ if (!isFound)
+ {
+ return false;
+ }
+
+ if(newDetailValue instanceof ImageDetail)
+ {
+ try
+ {
+ VCard v1 = new VCard();
+ v1.load(jabberProvider.getConnection());
+
+ v1.setAvatar(((ImageDetail) newDetailValue).getBytes());
+ v1.save(jabberProvider.getConnection());
+
+ return true;
+ } catch (XMPPException xmppe)
+ {
+ xmppe.printStackTrace();
+ }
+ }
+
return false;
}
diff --git a/src/net/java/sip/communicator/impl/protocol/msn/ContactGroupMsnImpl.java b/src/net/java/sip/communicator/impl/protocol/msn/ContactGroupMsnImpl.java
index 9834b2b..5217d35 100644
--- a/src/net/java/sip/communicator/impl/protocol/msn/ContactGroupMsnImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/msn/ContactGroupMsnImpl.java
@@ -84,7 +84,7 @@ public class ContactGroupMsnImpl
for (MsnContact groupMember : groupMembers)
{
/*
- *Only add the contact if it doesn't already exist in some other
+ * Only add the contact if it doesn't already exist in some other
* group. This is necessary because MSN would allow having one and
* the same contact in more than one group.
*/
diff --git a/src/net/java/sip/communicator/impl/protocol/msn/MsnActivator.java b/src/net/java/sip/communicator/impl/protocol/msn/MsnActivator.java
index dc765e1..e29c598 100644
--- a/src/net/java/sip/communicator/impl/protocol/msn/MsnActivator.java
+++ b/src/net/java/sip/communicator/impl/protocol/msn/MsnActivator.java
@@ -9,9 +9,11 @@ package net.java.sip.communicator.impl.protocol.msn;
import java.util.*;
import net.java.sip.communicator.service.configuration.*;
+import net.java.sip.communicator.service.fileaccess.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.resources.*;
+import net.java.sip.communicator.util.*;
import org.osgi.framework.*;
/**
@@ -27,6 +29,7 @@ public class MsnActivator
private ServiceRegistration msnPpFactoryServReg = null;
private static BundleContext bundleContext = null;
private static ConfigurationService configurationService = null;
+ private static FileAccessService fileAccessService = null;
private static ProtocolProviderFactoryMsnImpl msnProviderFactory = null;
@@ -144,4 +147,21 @@ public class MsnActivator
= ResourceManagementServiceUtils.getService(bundleContext);
return resources;
}
+
+ /**
+ * Returns the <tt>FileAccessService</tt> obtained from the bundle context.
+ *
+ * @return the <tt>FileAccessService</tt> obtained from the bundle context
+ */
+ public static FileAccessService getFileAccessService()
+ {
+ if (fileAccessService == null)
+ {
+ fileAccessService
+ = ServiceUtils.getService(
+ bundleContext,
+ FileAccessService.class);
+ }
+ return fileAccessService;
+ }
}
diff --git a/src/net/java/sip/communicator/impl/protocol/msn/OperationSetAvatarMsnImpl.java b/src/net/java/sip/communicator/impl/protocol/msn/OperationSetAvatarMsnImpl.java
new file mode 100644
index 0000000..d65389d
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/msn/OperationSetAvatarMsnImpl.java
@@ -0,0 +1,26 @@
+/*
+ * SIP Communicator, 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.protocol.msn;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * A simple implementation of the <tt>OperationSetAvatar</tt> interface for the
+ * msn protocol.
+ *
+ * @author Damian Minkov
+ */
+public class OperationSetAvatarMsnImpl
+ extends AbstractOperationSetAvatar<ProtocolProviderServiceMsnImpl>
+{
+ public OperationSetAvatarMsnImpl(
+ ProtocolProviderServiceMsnImpl parentProvider,
+ OperationSetServerStoredAccountInfo accountInfoOpSet)
+ {
+ super(parentProvider, accountInfoOpSet, 0, 0, 0);
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/msn/OperationSetServerStoredAccountInfoMsnImpl.java b/src/net/java/sip/communicator/impl/protocol/msn/OperationSetServerStoredAccountInfoMsnImpl.java
new file mode 100644
index 0000000..014ce4c
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/msn/OperationSetServerStoredAccountInfoMsnImpl.java
@@ -0,0 +1,553 @@
+/*
+ * SIP Communicator, 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.protocol.msn;
+
+import java.io.*;
+import java.util.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.ServerStoredDetails.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+import net.sf.jml.*;
+
+import javax.imageio.*;
+
+/**
+ * Saves account avatar image. If one is already saved we set it as initial one
+ * for the MsnOwner.
+ *
+ * @author SR
+ * @author Damian Minkov
+ */
+public class OperationSetServerStoredAccountInfoMsnImpl
+ implements OperationSetServerStoredAccountInfo,
+ RegistrationStateChangeListener
+{
+ /**
+ * Logger for this class.
+ */
+ private static final Logger logger =
+ Logger.getLogger(OperationSetServerStoredAccountInfoMsnImpl.class);
+
+ /**
+ * The jabber provider that created us.
+ */
+ private ProtocolProviderServiceMsnImpl msnProvider = null;
+ /**
+ * Our account uin=email address.
+ */
+ private String uin = null;
+
+ /**
+ * A place to store our own picture.
+ */
+ private static final String STORE_DIR = "avatarcache" + File.separator;
+
+ /**
+ * Here is kept all the details retrieved so far.
+ */
+ private Hashtable<String,List<GenericDetail>> retrievedDetails
+ = new Hashtable<String,List<GenericDetail>>();
+
+ protected OperationSetServerStoredAccountInfoMsnImpl(
+ ProtocolProviderServiceMsnImpl msnProvider,
+ String uin)
+ {
+ this.msnProvider = msnProvider;
+ this.uin = uin;
+
+ this.msnProvider.addRegistrationStateChangeListener(this);
+ }
+
+ /**
+ * Returns an iterator over all details that are instances or descendants of
+ * the specified class. If for example an our account has a work address
+ * and an address detail, a call to this method with AddressDetail.class
+ * would return both of them.
+ * <p>
+ * @param detailClass one of the detail classes defined in the
+ * ServerStoredDetails class, indicating the kind of details we're
+ * interested in.
+ * <p>
+ * @return a java.util.Iterator over all details that are instances or
+ * descendants of the specified class.
+ */
+ public Iterator<GenericDetail> getDetailsAndDescendants(Class detailClass)
+ {
+ assertConnected();
+
+ List<GenericDetail> details = getContactDetails(uin);
+ List<GenericDetail> result = new LinkedList<GenericDetail>();
+
+ Iterator<GenericDetail> iter = details.iterator();
+ while (iter.hasNext())
+ {
+ GenericDetail item = iter.next();
+ if (detailClass.isInstance(item))
+ {
+ result.add(item);
+ }
+ }
+
+ return result.iterator();
+ }
+
+ /**
+ * request the full info for the given contactAddress
+ * waits and return this details
+ *
+ * @param contactAddress String
+ * @return Vector the details
+ */
+ List<GenericDetail> getContactDetails(String contactAddress)
+ {
+ List<GenericDetail> result = retrievedDetails.get(contactAddress);
+
+ if (result == null)
+ {
+ result = new LinkedList<GenericDetail>();
+ try
+ {
+ MsnMessenger messenger = msnProvider.getMessenger();
+
+ if (messenger == null)
+ {
+ return null;
+ }
+
+ Email email = Email.parseStr(contactAddress);
+
+ String tmp = null;
+ byte[] imageBytes;
+ if (messenger.getOwner().getEmail().equals(email))
+ {
+ MsnOwner owner = messenger.getOwner();
+ tmp = owner.getDisplayName();
+ result.add(new ServerStoredDetails.DisplayNameDetail(tmp));
+
+ MsnObject image = owner.getDisplayPicture();
+ if (image != null)
+ {
+ imageBytes = image.getMsnObj();
+ if (imageBytes != null && imageBytes.length > 0)
+ {
+ result.add(new ServerStoredDetails.ImageDetail(
+ "Image", imageBytes));
+ }
+ }
+ } else
+ {
+ MsnContact contact =
+ messenger.getContactList().getContactByEmail(email);
+ tmp = contact.getDisplayName();
+ result.add(new ServerStoredDetails.DisplayNameDetail(tmp));
+ imageBytes = contact.getAvatar().getMsnObj();
+ if (imageBytes != null && imageBytes.length > 0)
+ {
+ result.add(new ServerStoredDetails.ImageDetail(
+ "Image", imageBytes));
+ }
+ }
+
+ } catch (Exception exc)
+ {
+ logger.error("Cannot load details for contact " + this
+ + " : " + exc.getMessage(), exc);
+ }
+ }
+
+ retrievedDetails.put(contactAddress, result);
+
+ return new LinkedList<GenericDetail>(result);
+ }
+
+ /**
+ * Returns an iterator over all details that are instances of exactly the
+ * same class as the one specified. Not that, contrary to the
+ * getDetailsAndDescendants() method this one would only return details
+ * that are instances of the specified class and not only its descendants.
+ * If for example our account has both a work address and an address detail,
+ * a call to this method with AddressDetail.class would return only the
+ * AddressDetail instance and not the WorkAddressDetail instance.
+ * <p>
+ * @param detailClass one of the detail classes defined in the
+ * ServerStoredDetails class, indicating the kind of details we're
+ * interested in.
+ * <p>
+ * @return a java.util.Iterator over all details of specified class.
+ */
+ public Iterator<GenericDetail> getDetails(Class detailClass)
+ {
+ assertConnected();
+
+ return getDetails(uin, detailClass);
+ }
+
+ /**
+ * Returns all details currently available and set for our account.
+ * <p>
+ * @return a java.util.Iterator over all details currently set our account.
+ */
+ public Iterator<GenericDetail> getAllAvailableDetails()
+ {
+ assertConnected();
+
+ return getContactDetails(uin).iterator();
+ }
+
+ /**
+ * Returns all detail Class-es that the underlying implementation supports
+ * setting. Note that if you call one of the modification methods (add
+ * remove or replace) with a detail not contained by the iterator returned
+ * by this method, an IllegalArgumentException will be thrown.
+ * <p>
+ * @return a java.util.Iterator over all detail classes supported by the
+ * implementation.
+ */
+ public Iterator<Class<? extends GenericDetail>> getSupportedDetailTypes()
+ {
+ List<GenericDetail> details = getContactDetails(uin);
+ List<Class<? extends GenericDetail>> result
+ = new LinkedList<Class<? extends GenericDetail>>();
+
+ Iterator<GenericDetail> iter = details.iterator();
+ while (iter.hasNext())
+ {
+ GenericDetail obj = iter.next();
+ result.add(obj.getClass());
+ }
+
+ return result.iterator();
+ }
+
+ /**
+ * Determines whether a detail class represents a detail supported by the
+ * underlying implementation or not. Note that if you call one of the
+ * modification methods (add remove or replace) with a detail that this
+ * method has determined to be unsupported (returned false) this would lead
+ * to an IllegalArgumentException being thrown.
+ * <p>
+ * @param detailClass the class the support for which we'd like to
+ * determine.
+ * <p>
+ * @return true if the underlying implementation supports setting details of
+ * this type and false otherwise.
+ */
+ public boolean isDetailClassSupported(
+ Class<? extends GenericDetail> detailClass)
+ {
+ List<GenericDetail> details = getContactDetails(uin);
+ Iterator<GenericDetail> iter = details.iterator();
+ while (iter.hasNext())
+ {
+ GenericDetail obj = iter.next();
+ if (detailClass.isAssignableFrom(obj.getClass()))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * The method returns the number of instances supported for a particular
+ * detail type. Some protocols offer storing multiple values for a
+ * particular detail type. Spoken languages are a good example.
+ * @param detailClass the class whose max instance number we'd like to find
+ * out.
+ * <p>
+ * @return int the maximum number of detail instances.
+ */
+ public int getMaxDetailInstances(Class detailClass)
+ {
+ return 1;
+ }
+
+ /**
+ * returns the user details from the specified class
+ * exactly that class not its descendants
+ *
+ * @param uin String
+ * @param detailClass Class
+ * @return Iterator
+ */
+ private Iterator<GenericDetail> getDetails(String uin, Class detailClass)
+ {
+ List<GenericDetail> details = getContactDetails(uin);
+ List<GenericDetail> result = new LinkedList<GenericDetail>();
+
+ Iterator<GenericDetail> iter = details.iterator();
+ while (iter.hasNext())
+ {
+ GenericDetail item = iter.next();
+ if (detailClass.equals(item.getClass()))
+ {
+ result.add(item);
+ }
+ }
+
+ return result.iterator();
+ }
+
+ /**
+ * Adds the specified detail to the list of details registered on-line
+ * for this account. If such a detail already exists its max instance number
+ * is consulted and if it allows it - a second instance is added or otherwise
+ * and illegal argument exception is thrown. An IllegalArgumentException is
+ * also thrown in case the class of the specified detail is not supported by
+ * the underlying implementation, i.e. its class name was not returned by the
+ * getSupportedDetailTypes() method.
+ * <p>
+ * @param detail the detail that we'd like registered on the server.
+ * <p>
+ * @throws IllegalArgumentException if such a detail already exists and its
+ * max instances number has been attained or if the underlying
+ * implementation does not support setting details of the corresponding
+ * class.
+ * @throws OperationFailedException with code Network Failure if putting the
+ * new value online has failed
+ * @throws java.lang.ArrayIndexOutOfBoundsException if the number of
+ * instances currently registered by the application is already equal to the
+ * maximum number of supported instances (@see getMaxDetailInstances())
+ */
+ public void addDetail(ServerStoredDetails.GenericDetail detail)
+ throws IllegalArgumentException,
+ OperationFailedException,
+ ArrayIndexOutOfBoundsException
+ {
+ assertConnected();
+
+ /*Currently as the function only provied the list of classes that currently have data associatd with them
+ * in Jabber InfoRetreiver we have to skip this check*/
+// if (!isDetailClassSupported(detail.getClass())) {
+// throw new IllegalArgumentException(
+// "implementation does not support such details " +
+// detail.getClass());
+// }
+
+ Iterator iter = getDetails(detail.getClass());
+ int currentDetailsSize = 0;
+ while (iter.hasNext())
+ {
+ currentDetailsSize++;
+ }
+ if (currentDetailsSize >= getMaxDetailInstances(detail.getClass())) {
+ throw new ArrayIndexOutOfBoundsException(
+ "Max count for this detail is already reached");
+ }
+
+ MsnOwner owner = msnProvider.getMessenger().getOwner();
+
+ if (detail instanceof ImageDetail)
+ {
+ try
+ {
+ String path = storePicture(((ImageDetail) detail).getBytes());
+
+ FileInputStream in = new FileInputStream(path);
+ byte[] b = new byte[in.available()];
+ in.read(b);
+ in.close();
+
+ owner.setDisplayPicture(MsnObject.getInstance(
+ owner.getEmail().getEmailAddress(),
+ b));
+ } catch(Exception e)
+ {
+ logger.error("Error setting own avatar.", e);
+ }
+ }
+ }
+
+ /**
+ * Stores the picture.
+ * @param data data to store
+ * @return the picture path.
+ * @throws Exception
+ */
+ private String storePicture(byte[] data)
+ throws Exception
+ {
+ String imagePath = STORE_DIR
+ + msnProvider.getAccountID().getAccountUniqueID() + ".jpg";
+
+ File storeDir = MsnActivator.getFileAccessService()
+ .getPrivatePersistentDirectory(STORE_DIR);
+
+ // if dir doesn't exist create it
+ storeDir.mkdirs();
+
+ File file = MsnActivator.getFileAccessService()
+ .getPrivatePersistentFile(imagePath);
+
+ ImageIO.write(
+ ImageIO.read(new ByteArrayInputStream(data)),
+ "jpg",
+ file);
+
+ return file.getPath();
+ }
+
+ /**
+ * Removes the specified detail from the list of details stored online for
+ * this account. The method returns a boolean indicating if such a detail
+ * was found (and removed) or not.
+ * <p>
+ * @param detail the detail to remove
+ * @return true if the specified detail existed and was successfully removed
+ * and false otherwise.
+ * @throws OperationFailedException with code Network Failure if removing the
+ * detail from the server has failed
+ */
+ public boolean removeDetail(ServerStoredDetails.GenericDetail detail)
+ throws OperationFailedException
+ {
+ return false;
+ }
+
+ /**
+ * Replaces the currentDetailValue detail with newDetailValue and returns
+ * true if the operation was a success or false if currentDetailValue did
+ * not previously exist (in this case an additional call to addDetail is
+ * required).
+ * <p>
+ * @param currentDetailValue the detail value we'd like to replace.
+ * @param newDetailValue the value of the detail that we'd like to replace
+ * currentDetailValue with.
+ * @throws ClassCastException if newDetailValue is not an instance of the
+ * same class as currentDetailValue.
+ * @throws OperationFailedException with code Network Failure if putting the
+ * new value back online has failed
+ */
+ public boolean replaceDetail(
+ ServerStoredDetails.GenericDetail currentDetailValue,
+ ServerStoredDetails.GenericDetail newDetailValue)
+ throws ClassCastException, OperationFailedException
+ {
+ assertConnected();
+
+ if (!newDetailValue.getClass().equals(currentDetailValue.getClass()))
+ {
+ throw new ClassCastException("New value to be replaced is not " +
+ "as the current one");
+ }
+ // if values are the same no change
+ if (currentDetailValue.equals(newDetailValue))
+ {
+ return true;
+ }
+
+ boolean isFound = false;
+ Iterator iter = getDetails(uin, currentDetailValue.getClass());
+ while (iter.hasNext())
+ {
+ GenericDetail item = (GenericDetail) iter.next();
+ if (item.equals(currentDetailValue))
+ {
+ isFound = true;
+ break;
+
+ }
+ }
+ // current detail value does not exist
+ if (!isFound)
+ {
+ return false;
+ }
+
+ MsnOwner owner = msnProvider.getMessenger().getOwner();
+
+ if (newDetailValue instanceof ImageDetail)
+ {
+ try
+ {
+ String path = storePicture(
+ ((ImageDetail) newDetailValue).getBytes());
+
+ FileInputStream in = new FileInputStream(path);
+ byte[] b = new byte[in.available()];
+ in.read(b);
+ in.close();
+
+ owner.setDisplayPicture(MsnObject.getInstance(
+ owner.getEmail().getEmailAddress(),
+ b));
+
+ return true;
+ } catch(Exception e)
+ {
+ logger.error("Error setting own avatar.", e);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Utility method throwing an exception if the icq stack is not properly
+ * initialized.
+ * @throws java.lang.IllegalStateException if the underlying ICQ stack is
+ * not registered and initialized.
+ */
+ private void assertConnected()
+ throws IllegalStateException
+ {
+ if (msnProvider == null)
+ {
+ throw new IllegalStateException(
+ "The jabber provider must be non-null and signed on "
+ + "before being able to communicate.");
+ }
+
+ if (!msnProvider.isRegistered())
+ {
+ throw new IllegalStateException(
+ "The jabber provider must be signed on before "
+ + "being able to communicate.");
+ }
+ }
+
+ /**
+ * The method is called by a <code>ProtocolProviderService</code>
+ * implementation whenever a change in the registration state of the
+ * corresponding provider had occurred.
+ *
+ * @param evt the event describing the status change.
+ */
+ public void registrationStateChanged(RegistrationStateChangeEvent evt)
+ {
+ if(evt.getNewState() == RegistrationState.REGISTERING)
+ {
+ try
+ {
+ String imagePath = STORE_DIR
+ + msnProvider.getAccountID().getAccountUniqueID() + ".jpg";
+
+ File file = MsnActivator.getFileAccessService()
+ .getPrivatePersistentFile(imagePath);
+
+ if(file.exists())
+ {
+ FileInputStream in = new FileInputStream(file);
+ byte[] b = new byte[in.available()];
+ in.read(b);
+ in.close();
+
+ MsnOwner owner = msnProvider.getMessenger().getOwner();
+
+ owner.setInitDisplayPicture(MsnObject.getInstance(
+ owner.getEmail().getEmailAddress(),
+ b));
+ }
+ }
+ catch(Exception ex)
+ {
+ logger.error("Cannot obtain own avatar image.", ex);
+ }
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/msn/ProtocolProviderServiceMsnImpl.java b/src/net/java/sip/communicator/impl/protocol/msn/ProtocolProviderServiceMsnImpl.java
index c46861a..7bc61f7 100644
--- a/src/net/java/sip/communicator/impl/protocol/msn/ProtocolProviderServiceMsnImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/msn/ProtocolProviderServiceMsnImpl.java
@@ -211,6 +211,12 @@ public class ProtocolProviderServiceMsnImpl
persistentPresence.setMessenger(messenger);
typingNotifications.setMessenger(messenger);
+ fireRegistrationStateChanged(
+ getRegistrationState(),
+ RegistrationState.REGISTERING,
+ RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
+ null);
+
try
{
messenger.login();
@@ -306,6 +312,17 @@ public class ProtocolProviderServiceMsnImpl
OperationSetPresence.class,
persistentPresence);
+ //initialize AccountInfo
+ OperationSetServerStoredAccountInfoMsnImpl accountInfo
+ = new OperationSetServerStoredAccountInfoMsnImpl(
+ this, screenname);
+ addSupportedOperationSet(
+ OperationSetServerStoredAccountInfo.class,
+ accountInfo);
+ addSupportedOperationSet(
+ OperationSetAvatar.class,
+ new OperationSetAvatarMsnImpl(this, accountInfo));
+
addSupportedOperationSet(
OperationSetAdHocMultiUserChat.class,
new OperationSetAdHocMultiUserChatMsnImpl(this));
diff --git a/src/net/java/sip/communicator/impl/protocol/msn/msn.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/msn/msn.provider.manifest.mf
index 5f33c55..dd8be41 100755
--- a/src/net/java/sip/communicator/impl/protocol/msn/msn.provider.manifest.mf
+++ b/src/net/java/sip/communicator/impl/protocol/msn/msn.provider.manifest.mf
@@ -28,7 +28,9 @@ Import-Package: org.apache.commons.logging,
javax.xml.parsers,
javax.xml.datatype,
sun.security.action,
+ javax.imageio,
net.java.sip.communicator.service.configuration,
+ net.java.sip.communicator.service.fileaccess,
net.java.sip.communicator.service.resources,
net.java.sip.communicator.util,
net.java.sip.communicator.service.configuration.event,
diff --git a/src/net/java/sip/communicator/service/protocol/event/ChatRoomMessageReceivedEvent.java b/src/net/java/sip/communicator/service/protocol/event/ChatRoomMessageReceivedEvent.java
index d980d92..0fdd67f 100644
--- a/src/net/java/sip/communicator/service/protocol/event/ChatRoomMessageReceivedEvent.java
+++ b/src/net/java/sip/communicator/service/protocol/event/ChatRoomMessageReceivedEvent.java
@@ -64,6 +64,11 @@ public class ChatRoomMessageReceivedEvent
private final int eventType;
/**
+ * Some services can fill our room with message history.
+ */
+ private boolean historyMessage = false;
+
+ /**
* Creates a <tt>MessageReceivedEvent</tt> representing reception of the
* <tt>source</tt> message received from the specified <tt>from</tt>
* contact.
@@ -138,4 +143,23 @@ public class ChatRoomMessageReceivedEvent
{
return eventType;
}
+
+ /**
+ * Is current event for history message.
+ * @return is current event for history message.
+ */
+ public boolean isHistoryMessage()
+ {
+ return historyMessage;
+ }
+
+ /**
+ * Changes property, whether this event is for a history message.
+ *
+ * @param historyMessage whether its event for history message.
+ */
+ public void setHistoryMessage(boolean historyMessage)
+ {
+ this.historyMessage = historyMessage;
+ }
}
diff --git a/src/net/java/sip/communicator/util/ImageUtils.java b/src/net/java/sip/communicator/util/ImageUtils.java
index 9b7d7d0..4d17686 100644
--- a/src/net/java/sip/communicator/util/ImageUtils.java
+++ b/src/net/java/sip/communicator/util/ImageUtils.java
@@ -259,4 +259,52 @@ public class ImageUtils
}
return image;
}
+
+ /**
+ * Returns the buffered image corresponding to the given image
+ * @param source an image
+ * @return the buffered image corresponding to the given image
+ */
+ public static BufferedImage getBufferedImage(Image source)
+ {
+ if (source == null)
+ {
+ return null;
+ }
+ else if (source instanceof BufferedImage)
+ {
+ return (BufferedImage) source;
+ }
+
+ int width = source.getWidth(null);
+ int height = source.getHeight(null);
+
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ Graphics graphics = image.createGraphics();
+ graphics.drawImage(source, 0, 0, null);
+ graphics.dispose();
+
+ return image;
+ }
+
+ /**
+ * Extracts bytes from image.
+ * @param image the image.
+ * @return the bytes of the image.
+ */
+ public static byte[] toByteArray(BufferedImage image)
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try
+ {
+ ImageIO.write(image, "png", out);
+ }
+ catch (IOException e)
+ {
+ logger.debug("Cannot convert buffered image to byte[]", e);
+ return null;
+ }
+
+ return out.toByteArray();
+ }
}
diff --git a/src/net/java/sip/communicator/util/swing/FramedImage.java b/src/net/java/sip/communicator/util/swing/FramedImage.java
index d74ff77..86db4d2 100644
--- a/src/net/java/sip/communicator/util/swing/FramedImage.java
+++ b/src/net/java/sip/communicator/util/swing/FramedImage.java
@@ -24,9 +24,9 @@ public class FramedImage
private ImageIcon icon;
- private final int width;
+ protected final int width;
- private final int height;
+ protected final int height;
/**
* Creates a FramedImage by specifying the width and the height of the
@@ -85,6 +85,31 @@ public class FramedImage
}
/**
+ * Sets the image to display in the frame.
+ *
+ * @param image the image to display in the frame
+ */
+ public void setImageIcon(Image image)
+ {
+ icon = ImageUtils.getScaledRoundedIcon(image, width - 2, height - 2);
+
+ if (this.isVisible())
+ {
+ this.revalidate();
+ this.repaint();
+ }
+ }
+
+ /**
+ * Returns the image that is shown.
+ * @return
+ */
+ public Image getImage()
+ {
+ return icon.getImage();
+ }
+
+ /**
* Paints the contained image in a frame.
*
* Overrides {@link JComponent#paintComponent(Graphics)}.
diff --git a/src/net/java/sip/communicator/util/swing/FramedImageWithMenu.java b/src/net/java/sip/communicator/util/swing/FramedImageWithMenu.java
new file mode 100644
index 0000000..90a1eca
--- /dev/null
+++ b/src/net/java/sip/communicator/util/swing/FramedImageWithMenu.java
@@ -0,0 +1,274 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.swing;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+import org.jvnet.lafwidget.animation.*;
+
+/**
+ * A custom component, used to show images in a frame. A rollover for the
+ * content image and optional menu in dialog.
+ *
+ * @author Damien Roth
+ */
+public class FramedImageWithMenu
+ extends FramedImage
+ implements MouseListener, PopupMenuListener
+{
+ /**
+ * The dialog containing the menu with actions.
+ */
+ private JPopupMenu popupMenu;
+
+ /**
+ * The parent frame.
+ */
+ private JFrame mainFrame;
+
+ /**
+ * Should we currently draw overlay.
+ */
+ private boolean drawOverlay = false;
+
+ /**
+ * Are we showing custom image or the default one.
+ */
+ private boolean isDefaultImage = true;
+
+ /**
+ * The current image.
+ */
+ private Image currentImage;
+
+ /**
+ * Creates the component.
+ * @param mainFrame the parent frame.
+ * @param imageIcon the image icon to show as default one.
+ * @param width width of component.
+ * @param height height of component.
+ */
+ public FramedImageWithMenu(
+ JFrame mainFrame,
+ ImageIcon imageIcon,
+ int width,
+ int height)
+ {
+ super(imageIcon, width, height);
+
+ this.mainFrame = mainFrame;
+ this.addMouseListener(this);
+ }
+
+ /**
+ * Sets the dialog used for menu for this Image.
+ * @param popupMenu the dialog to show as menu. Can be null if no menu
+ * will be available.
+ */
+ public void setPopupMenu(JPopupMenu popupMenu)
+ {
+ this.popupMenu = popupMenu;
+ if(popupMenu != null)
+ this.popupMenu.addPopupMenuListener(this);
+ }
+
+ /**
+ * Sets the image to display in the frame.
+ *
+ * @param imageIcon the image to display in the frame
+ */
+ public void setImageIcon(ImageIcon imageIcon)
+ {
+ // Intercept the action to validate the user icon and not the default
+ super.setImageIcon(imageIcon.getImage());
+ this.isDefaultImage = false;
+
+ this.currentImage = imageIcon.getImage();
+ }
+
+ /**
+ * Returns the current image with no rounded corners. Only return the user
+ * image and not the default image.
+ *
+ * @return the current image - null if it's the default image
+ */
+ public Image getAvatar()
+ {
+ return (!this.isDefaultImage) ? this.currentImage : this.getImage();
+ }
+
+ @Override
+ public void paintComponent(Graphics g)
+ {
+ super.paintComponent(g);
+
+ if (drawOverlay)
+ {
+ g = g.create();
+ AntialiasingManager.activateAntialiasing(g);
+
+ try
+ {
+ // Paint a roll over fade out.
+ FadeTracker fadeTracker = FadeTracker.getInstance();
+
+ float visibility = 0.0f;
+ if (fadeTracker.isTracked(this, FadeKind.ROLLOVER))
+ {
+ visibility = fadeTracker.getFade(this, FadeKind.ROLLOVER);
+ visibility /= 4;
+ }
+ else
+ visibility = 0.5f;
+
+ // Draw black overlay
+ g.setColor(new Color(0.0f, 0.0f, 0.0f, visibility));
+ g.fillRoundRect(1, 1, width - 2, height - 2, 10, 10);
+
+ // Draw arrow
+ g.setColor(Color.WHITE);
+
+ int[] arrowX = new int[] {
+ width - 17,
+ width - 7,
+ width - 12
+ };
+ int[] arrowY = new int[] {
+ height - 12,
+ height - 12,
+ height - 7
+ };
+ g.fillPolygon(arrowX, arrowY, arrowX.length);
+ }
+ finally
+ {
+ g.dispose();
+ }
+ }
+ }
+
+ /**
+ * Show the avatar dialog as a glasspane of the mainframe
+ *
+ * @param show show dialogs if sets to TRUE - hide otherwise
+ */
+ private void showDialog(MouseEvent e, boolean show)
+ {
+ if (this.popupMenu == null)
+ {
+ return;
+ }
+
+ if (show)
+ {
+ Point imageLoc = this.getLocationOnScreen();
+ Point rootPaneLoc = mainFrame.getRootPane().getLocationOnScreen();
+
+ this.popupMenu.setSize(mainFrame.getRootPane().getWidth(),
+ this.popupMenu.getHeight());
+
+ this.popupMenu.show(this, (rootPaneLoc.x - imageLoc.x),
+ this.getHeight());
+ }
+ else
+ {
+ this.drawOverlay = false;
+ this.repaint();
+ }
+ }
+
+ public void mouseEntered(MouseEvent e)
+ {
+ if (this.drawOverlay)
+ return;
+
+ this.drawOverlay = true;
+
+ FadeTracker fadeTracker = FadeTracker.getInstance();
+
+ fadeTracker.trackFadeIn(FadeKind.ROLLOVER,
+ FramedImageWithMenu.this,
+ true,
+ new AvatarRepaintCallback());
+ }
+
+ public void mouseExited(MouseEvent e)
+ {
+ // Remove overlay only if the dialog isn't visible
+ if (!popupMenu.isVisible())
+ {
+ this.drawOverlay = false;
+ this.repaint();
+ }
+ }
+
+ public void mouseReleased(MouseEvent e)
+ {
+ showDialog(e, !popupMenu.isVisible());
+ }
+
+ /**
+ * This method is called before the popup menu becomes visible
+ */
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
+
+ /**
+ * This method is called before the popup menu becomes invisible
+ * Note that a JPopupMenu can become invisible any time
+ */
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
+ {
+ this.drawOverlay = false;
+ this.repaint();
+ }
+
+ /**
+ * This method is called when the popup menu is canceled
+ */
+ public void popupMenuCanceled(PopupMenuEvent e){}
+
+ /**
+ * The <tt>ButtonRepaintCallback</tt> is charged to repaint this button
+ * when the fade animation is performed.
+ */
+ private class AvatarRepaintCallback
+ implements FadeTrackerCallback
+ {
+ public void fadeEnded(FadeKind arg0)
+ {
+ repaintLater();
+ }
+
+ public void fadePerformed(FadeKind arg0, float arg1)
+ {
+ repaintLater();
+ }
+
+ private void repaintLater()
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ FramedImageWithMenu.this.repaint();
+ }
+ });
+ }
+
+ public void fadeReversed(FadeKind arg0, boolean arg1, float arg2)
+ {
+ }
+ }
+
+ public void mouseClicked(MouseEvent e) {}
+
+ public void mousePressed(MouseEvent e) {}
+}
diff --git a/src/net/java/sip/communicator/util/swing/SIPCommLinkButton.java b/src/net/java/sip/communicator/util/swing/SIPCommLinkButton.java
new file mode 100644
index 0000000..ae43b4f
--- /dev/null
+++ b/src/net/java/sip/communicator/util/swing/SIPCommLinkButton.java
@@ -0,0 +1,276 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.swing;
+
+import net.java.sip.communicator.util.swing.plaf.*;
+
+import java.awt.*;
+import java.net.*;
+
+import javax.swing.*;
+
+/**
+ * A button which text is a link. The button looks like a link.
+ */
+public class SIPCommLinkButton
+ extends JButton
+{
+ private static final long serialVersionUID = 1L;
+
+ private static final String UIClassID = "LinkButtonUI";
+
+ public static final int ALWAYS_UNDERLINE = 0;
+
+ public static final int HOVER_UNDERLINE = 1;
+
+ public static final int NEVER_UNDERLINE = 2;
+
+ private int linkBehavior;
+
+ private Color linkColor;
+
+ private Color colorPressed;
+
+ private Color visitedLinkColor;
+
+ private Color disabledLinkColor;
+
+ private URL buttonURL;
+
+ private boolean isLinkVisited;
+
+ /**
+ * Created Link Button.
+ */
+ public SIPCommLinkButton()
+ {
+ this(null, null);
+ }
+
+ /**
+ * Created Link Button with text.
+ * @param text
+ */
+ public SIPCommLinkButton(String text)
+ {
+ this(text, null);
+ }
+
+ /**
+ * Created Link Button with url.
+ * @param url
+ */
+ public SIPCommLinkButton(URL url)
+ {
+ this(null, url);
+ }
+
+ /**
+ * Created Link Button with text and url.
+ * @param text
+ * @param url
+ */
+ public SIPCommLinkButton(String text, URL url)
+ {
+ super(text);
+
+ // Define UI
+ this.setUI(SIPCommLinkButtonUI.createUI(this));
+ UIManager.getDefaults().put("LinkButtonUI", "SIPCommLinkButtonUI");
+ linkBehavior = SIPCommLinkButton.HOVER_UNDERLINE;
+
+ linkColor = Color.blue;
+ colorPressed = Color.red;
+ visitedLinkColor = new Color(128, 0, 128);
+
+ if (text == null && url != null)
+ this.setText(url.toExternalForm());
+ setLinkURL(url);
+
+ this.setBorderPainted(false);
+ this.setContentAreaFilled(false);
+ this.setRolloverEnabled(true);
+ this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ }
+
+ @Override
+ public void updateUI()
+ {
+ this.setUI(SIPCommLinkButtonUI.createUI(this));
+ }
+
+ public String getUIClassID()
+ {
+ return SIPCommLinkButton.UIClassID;
+ }
+
+ /**
+ * Setup the tooltip.
+ */
+ protected void setupToolTipText()
+ {
+ String tip = null;
+ if (buttonURL != null)
+ tip = buttonURL.toExternalForm();
+ setToolTipText(tip);
+ }
+
+ /**
+ * Changes link behaviour.
+ * @param bnew the new behaviour. One of ALWAYS_UNDERLINE, HOVER_UNDERLINE
+ * and NEVER_UNDERLINE.
+ */
+ public void setLinkBehavior(int bnew)
+ {
+ if (bnew != ALWAYS_UNDERLINE && bnew != HOVER_UNDERLINE
+ && bnew != NEVER_UNDERLINE)
+ throw new IllegalArgumentException("Not a legal LinkBehavior");
+
+ int old = linkBehavior;
+ linkBehavior = bnew;
+ firePropertyChange("linkBehavior", old, bnew);
+ repaint();
+ }
+
+ /**
+ * Returns the link behaviour.
+ * @return the link behaviour.
+ */
+ public int getLinkBehavior()
+ {
+ return linkBehavior;
+ }
+
+ /**
+ * Sets the link color.
+ * @param color the new color.
+ */
+ public void setLinkColor(Color color)
+ {
+ Color colorOld = linkColor;
+ linkColor = color;
+ firePropertyChange("linkColor", colorOld, color);
+ repaint();
+ }
+
+ /**
+ * Return the link color.
+ * @return link color.
+ */
+ public Color getLinkColor()
+ {
+ return linkColor;
+ }
+
+ /**
+ * Sets the active link color.
+ * @param colorNew the new color.
+ */
+ public void setActiveLinkColor(Color colorNew)
+ {
+ Color colorOld = colorPressed;
+ colorPressed = colorNew;
+ firePropertyChange("activeLinkColor", colorOld, colorNew);
+ repaint();
+ }
+
+ /**
+ * Returns the active link color.
+ * @return the active link color.
+ */
+ public Color getActiveLinkColor()
+ {
+ return colorPressed;
+ }
+
+ /**
+ * Sets disabled link color.
+ * @param color the new color.
+ */
+ public void setDisabledLinkColor(Color color)
+ {
+ Color colorOld = disabledLinkColor;
+ disabledLinkColor = color;
+ firePropertyChange("disabledLinkColor", colorOld, color);
+ if (!isEnabled())
+ repaint();
+ }
+
+ /**
+ * Returns the disabled link color.
+ * @return the disabled link color.
+ */
+ public Color getDisabledLinkColor()
+ {
+ return disabledLinkColor;
+ }
+
+ /**
+ * Set visited link color.
+ * @param colorNew the new visited link color.
+ */
+ public void setVisitedLinkColor(Color colorNew)
+ {
+ Color colorOld = visitedLinkColor;
+ visitedLinkColor = colorNew;
+ firePropertyChange("visitedLinkColor", colorOld, colorNew);
+ repaint();
+ }
+
+ /**
+ * Returns visited link color.
+ * @return visited link color.
+ */
+ public Color getVisitedLinkColor()
+ {
+ return visitedLinkColor;
+ }
+
+ /**
+ * Set a link.
+ * @param url the url.
+ */
+ public void setLinkURL(URL url)
+ {
+ URL urlOld = buttonURL;
+ buttonURL = url;
+ setupToolTipText();
+ firePropertyChange("linkURL", urlOld, url);
+ revalidate();
+ repaint();
+ }
+
+ /**
+ * Returns the url.
+ * @return the link url.
+ */
+ public URL getLinkURL()
+ {
+ return buttonURL;
+ }
+
+ /**
+ * Set a link visited.
+ * @param flagNew is link visited.
+ */
+ public void setLinkVisited(boolean flagNew)
+ {
+ boolean flagOld = isLinkVisited;
+ isLinkVisited = flagNew;
+ firePropertyChange("linkVisited", flagOld, flagNew);
+ repaint();
+ }
+
+ /**
+ * Returns is link visited.
+ * @return is link visited.
+ */
+ public boolean isLinkVisited()
+ {
+ return isLinkVisited;
+ }
+}
diff --git a/src/net/java/sip/communicator/util/swing/plaf/SIPCommLinkButtonUI.java b/src/net/java/sip/communicator/util/swing/plaf/SIPCommLinkButtonUI.java
new file mode 100644
index 0000000..1885bf6
--- /dev/null
+++ b/src/net/java/sip/communicator/util/swing/plaf/SIPCommLinkButtonUI.java
@@ -0,0 +1,75 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.swing.plaf;
+
+import net.java.sip.communicator.util.swing.*;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.*;
+
+/**
+ * The SIPCommLinkButtonUI implementation.
+ * @author ROTH Damien
+ */
+public class SIPCommLinkButtonUI
+ extends BasicButtonUI
+{
+ private static final SIPCommLinkButtonUI ui = new SIPCommLinkButtonUI();
+
+ public static ComponentUI createUI(JComponent jcomponent)
+ {
+ return ui;
+ }
+
+ protected void paintText(
+ Graphics g, JComponent com, Rectangle rect, String s)
+ {
+ SIPCommLinkButton bn = (SIPCommLinkButton) com;
+
+ ButtonModel bnModel = bn.getModel();
+ if (bnModel.isEnabled())
+ {
+ if (bnModel.isPressed())
+ bn.setForeground(bn.getActiveLinkColor());
+ else if (bn.isLinkVisited())
+ bn.setForeground(bn.getVisitedLinkColor());
+ else
+ bn.setForeground(bn.getLinkColor());
+ }
+ else
+ {
+ if (bn.getDisabledLinkColor() != null)
+ bn.setForeground(bn.getDisabledLinkColor());
+ }
+
+ super.paintText(g, com, rect, s);
+ int behaviour = bn.getLinkBehavior();
+
+ if (!(behaviour == SIPCommLinkButton.HOVER_UNDERLINE
+ && bnModel.isRollover())
+ && behaviour != SIPCommLinkButton.ALWAYS_UNDERLINE)
+ return;
+
+ FontMetrics fm = g.getFontMetrics();
+ int x = rect.x + getTextShiftOffset();
+ int y = (rect.y + fm.getAscent()
+ + fm.getDescent() + getTextShiftOffset()) - 1;
+ if (bnModel.isEnabled())
+ {
+ g.setColor(bn.getForeground());
+ g.drawLine(x, y, (x + rect.width) - 1, y);
+ }
+ else
+ {
+ g.setColor(bn.getBackground().brighter());
+ g.drawLine(x, y, (x + rect.width) - 1, y);
+ }
+ }
+}