aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl
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/impl
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/impl')
-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
20 files changed, 2343 insertions, 11 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,