aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build.xml4
-rw-r--r--ide/eclipse/.classpath2
-rw-r--r--ide/nbproject/project.xml2
-rw-r--r--lib/installer-exclude/jml-1.0b2.jarbin377361 -> 0 bytes
-rw-r--r--lib/installer-exclude/jml-1.0b5.jarbin0 -> 324477 bytes
-rw-r--r--resources/images/images.properties2
-rw-r--r--resources/images/impl/gui/buttons/magnifier_zoom_in.pngbin0 -> 680 bytes
-rw-r--r--resources/images/impl/gui/buttons/magnifier_zoom_out.pngbin0 -> 657 bytes
-rw-r--r--resources/languages/resources.properties17
-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
35 files changed, 3090 insertions, 17 deletions
diff --git a/build.xml b/build.xml
index 5fccf8c..5785e7d 100644
--- a/build.xml
+++ b/build.xml
@@ -1389,7 +1389,7 @@ javax.swing.event, javax.swing.border"/>
manifest="${src}/net/java/sip/communicator/impl/protocol/msn/msn.provider.manifest.mf">
<zipfileset dir="${dest}/net/java/sip/communicator/impl/protocol/msn"
prefix="net/java/sip/communicator/impl/protocol/msn"/>
- <zipfileset src="${lib.noinst}/jml-1.0b2.jar" prefix=""/>
+ <zipfileset src="${lib.noinst}/jml-1.0b5.jar" prefix=""/>
</jar>
</target>
@@ -1400,7 +1400,7 @@ javax.swing.event, javax.swing.border"/>
manifest="${testsrc}/net/java/sip/communicator/slick/protocol/msn/msn.provider.slick.manifest.mf">
<zipfileset dir="${dest}/net/java/sip/communicator/slick/protocol/msn"
prefix="net/java/sip/communicator/slick/protocol/msn"/>
- <zipfileset src="${lib.noinst}/jml-1.0b2.jar" prefix=""/>
+ <zipfileset src="${lib.noinst}/jml-1.0b5.jar" prefix=""/>
<zipfileset dir="${dest}/net/java/sip/communicator/slick/protocol/generic"
prefix="net/java/sip/communicator/slick/protocol/generic"/>
</jar>
diff --git a/ide/eclipse/.classpath b/ide/eclipse/.classpath
index 5b2eea0..d34efd1 100644
--- a/ide/eclipse/.classpath
+++ b/ide/eclipse/.classpath
@@ -23,7 +23,7 @@
<classpathentry kind="lib" path="lib/installer-exclude/jdom.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/jfontchooser-1.0.5.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/jmf.jar"/>
- <classpathentry kind="lib" path="lib/installer-exclude/jml-1.0b2.jar"/>
+ <classpathentry kind="lib" path="lib/installer-exclude/jml-1.0b5.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/jna.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/joscar-client.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/joscar-common.jar"/>
diff --git a/ide/nbproject/project.xml b/ide/nbproject/project.xml
index e88d18a..1b87782 100644
--- a/ide/nbproject/project.xml
+++ b/ide/nbproject/project.xml
@@ -123,7 +123,7 @@
<compilation-unit>
<package-root>src</package-root>
<classpath
- mode="compile">lib/felix.jar:lib/jdic-all.jar:lib/bundle/junit.jar:lib/bundle/log4j.jar:lib/bundle/commons-logging.jar:lib/installer-exclude/concurrent.jar:lib/installer-exclude/dict4j.jar:lib/installer-exclude/dnsjava-2.0.3.jar:lib/installer-exclude/jain-sip-api.jar:lib/installer-exclude/jain-sip-ri.jar:lib/installer-exclude/jain-sdp.jar:lib/installer-exclude/jcalendar-1.3.2.jar:lib/installer-exclude/jdic_misc.jar:lib/installer-exclude/jdom.jar:lib/installer-exclude/jmf.jar:lib/installer-exclude/jml-1.0b2.jar:lib/installer-exclude/joscar-client.jar:lib/installer-exclude/joscar-common.jar:lib/installer-exclude/joscar-protocol.jar:lib/installer-exclude/jsocks-klea.jar:lib/installer-exclude/jspeex.jar:lib/installer-exclude/junit.jar:lib/installer-exclude/log4j-1.2.8.jar:lib/installer-exclude/nist-sdp-1.0.jar:lib/installer-exclude/rome-0.9.jar:lib/installer-exclude/smack.jar:lib/installer-exclude/smackx.jar:lib/installer-exclude/Stun4J.jar:lib/installer-exclude/ymsg_network_v0_67.jar:lib/installer-exclude/fmj.jar:lib/installer-exclude/jna.jar:lib/installer-exclude/lti-civil-no_s_w_t.jar:lib/installer-exclude/swing-worker-1.2.jar:lib/os-specific/linux/installer-exclude/jmf.jar:lib/os-specific/linux/jdic_stub.jar:lib/os-specific/mac/OrangeExtensions.jar:lib/os-specific/mac/growl4j.jar:lib/os-specific/mac/jdic_stub.jar:lib/os-specific/mac/installer-exclude/jmf.jar:lib/os-specific/mac/installer-exclude/dock.jar:lib/os-specific/windows/jdic_stub.jar:lib/os-specific/windows/installer-exclude/jmf.jar:lib/os-specific/windows/installer-exclude/sound.jar:lib/installer-exclude/aclibico-2.1.jar:lib/installer-exclude/jdic_misc.jar:lib/installer-exclude/pircbot.jar:lib/os-specific/solaris/jdic_stub.jar:lib/os-specific/solaris/installer-exclude/jmf.jar:lib/installer-exclude/jsch-0.1.36.jar:lib/installer-exclude/apache-ant-1.7.0.jar:lib/installer-exclude/izpack-shortcut-link.jar:lib/installer-exclude/jfontchooser-1.0.5.jar:lib/installer-exclude/laf-widget.jar:lib/installer-exclude/transparency.jar:lib/installer-exclude/zrtp4j-light.jar:lib/installer-exclude/lcrypto-jdk16-143.jar:lib/installer-exclude/otr4j.jar:lib/installer-exclude/profiler4j-1.0-beta3-SC.jar:lib/installer-exclude/httpcore-4.0.1.jar:lib/installer-exclude/httpclient-4.0.1.jar:lib/installer-exclude/json-20090723.jar</classpath>
+ mode="compile">lib/felix.jar:lib/jdic-all.jar:lib/bundle/junit.jar:lib/bundle/log4j.jar:lib/bundle/commons-logging.jar:lib/installer-exclude/concurrent.jar:lib/installer-exclude/dict4j.jar:lib/installer-exclude/dnsjava-2.0.3.jar:lib/installer-exclude/jain-sip-api.jar:lib/installer-exclude/jain-sip-ri.jar:lib/installer-exclude/jain-sdp.jar:lib/installer-exclude/jcalendar-1.3.2.jar:lib/installer-exclude/jdic_misc.jar:lib/installer-exclude/jdom.jar:lib/installer-exclude/jmf.jar:lib/installer-exclude/jml-1.0b5.jar:lib/installer-exclude/joscar-client.jar:lib/installer-exclude/joscar-common.jar:lib/installer-exclude/joscar-protocol.jar:lib/installer-exclude/jsocks-klea.jar:lib/installer-exclude/jspeex.jar:lib/installer-exclude/junit.jar:lib/installer-exclude/log4j-1.2.8.jar:lib/installer-exclude/nist-sdp-1.0.jar:lib/installer-exclude/rome-0.9.jar:lib/installer-exclude/smack.jar:lib/installer-exclude/smackx.jar:lib/installer-exclude/Stun4J.jar:lib/installer-exclude/ymsg_network_v0_67.jar:lib/installer-exclude/fmj.jar:lib/installer-exclude/jna.jar:lib/installer-exclude/lti-civil-no_s_w_t.jar:lib/installer-exclude/swing-worker-1.2.jar:lib/os-specific/linux/installer-exclude/jmf.jar:lib/os-specific/linux/jdic_stub.jar:lib/os-specific/mac/OrangeExtensions.jar:lib/os-specific/mac/growl4j.jar:lib/os-specific/mac/jdic_stub.jar:lib/os-specific/mac/installer-exclude/jmf.jar:lib/os-specific/mac/installer-exclude/dock.jar:lib/os-specific/windows/jdic_stub.jar:lib/os-specific/windows/installer-exclude/jmf.jar:lib/os-specific/windows/installer-exclude/sound.jar:lib/installer-exclude/aclibico-2.1.jar:lib/installer-exclude/jdic_misc.jar:lib/installer-exclude/pircbot.jar:lib/os-specific/solaris/jdic_stub.jar:lib/os-specific/solaris/installer-exclude/jmf.jar:lib/installer-exclude/jsch-0.1.36.jar:lib/installer-exclude/apache-ant-1.7.0.jar:lib/installer-exclude/izpack-shortcut-link.jar:lib/installer-exclude/jfontchooser-1.0.5.jar:lib/installer-exclude/laf-widget.jar:lib/installer-exclude/transparency.jar:lib/installer-exclude/zrtp4j-light.jar:lib/installer-exclude/lcrypto-jdk16-143.jar:lib/installer-exclude/otr4j.jar:lib/installer-exclude/profiler4j-1.0-beta3-SC.jar:lib/installer-exclude/httpcore-4.0.1.jar:lib/installer-exclude/httpclient-4.0.1.jar:lib/installer-exclude/json-20090723.jar</classpath>
<built-to>classes</built-to>
<source-level>1.5</source-level>
</compilation-unit>
diff --git a/lib/installer-exclude/jml-1.0b2.jar b/lib/installer-exclude/jml-1.0b2.jar
deleted file mode 100644
index 0f820f9..0000000
--- a/lib/installer-exclude/jml-1.0b2.jar
+++ /dev/null
Binary files differ
diff --git a/lib/installer-exclude/jml-1.0b5.jar b/lib/installer-exclude/jml-1.0b5.jar
new file mode 100644
index 0000000..0a8765a
--- /dev/null
+++ b/lib/installer-exclude/jml-1.0b5.jar
Binary files differ
diff --git a/resources/images/images.properties b/resources/images/images.properties
index 4fc67e8..9fce8e2 100644
--- a/resources/images/images.properties
+++ b/resources/images/images.properties
@@ -180,6 +180,8 @@ service.gui.buttons.CALL_HISTORY_BUTTON=resources/images/impl/gui/buttons/callHi
service.gui.buttons.CALL_HISTORY_BUTTON_PRESSED=resources/images/impl/gui/buttons/callHistoryButtonPressed.png
service.gui.buttons.SEARCH_CALL_ICON=resources/images/impl/gui/buttons/searchCallIcon.png
service.gui.buttons.SEARCH_CALL_ROLLOVER_ICON=resources/images/impl/gui/buttons/searchCallRolloverIcon.png
+service.gui.buttons.ZOOM_OUT=resources/images/impl/gui/buttons/magnifier_zoom_out.png
+service.gui.buttons.ZOOM_IN=resources/images/impl/gui/buttons/magnifier_zoom_in.png
# Sound level icons
service.gui.soundlevel.SOUND_LEVEL_ACTIVE=resources/images/impl/gui/common/soundlevel/soundActive.png
diff --git a/resources/images/impl/gui/buttons/magnifier_zoom_in.png b/resources/images/impl/gui/buttons/magnifier_zoom_in.png
new file mode 100644
index 0000000..af4fe07
--- /dev/null
+++ b/resources/images/impl/gui/buttons/magnifier_zoom_in.png
Binary files differ
diff --git a/resources/images/impl/gui/buttons/magnifier_zoom_out.png b/resources/images/impl/gui/buttons/magnifier_zoom_out.png
new file mode 100644
index 0000000..81f2819
--- /dev/null
+++ b/resources/images/impl/gui/buttons/magnifier_zoom_out.png
Binary files differ
diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties
index e1a840b..ad0106d 100644
--- a/resources/languages/resources.properties
+++ b/resources/languages/resources.properties
@@ -483,6 +483,23 @@ service.gui.CONTINUE=Continue
service.gui.SHOW_CERT=Show Certificate
service.gui.HIDE_CERT=Hide Certificate
+service.gui.avatar.CHOOSE_ICON=Choose icon ...
+service.gui.avatar.CLEAR_RECENT=Clear recent pictures
+service.gui.avatar.RECENT_ICONS=Recent icons:
+# service.gui.avatar.imagepicker
+service.gui.avatar.imagepicker.CANCEL=Cancel
+service.gui.avatar.imagepicker.CHOOSE_FILE=Choose ...
+service.gui.avatar.imagepicker.CLICK=Click and Smile
+service.gui.avatar.imagepicker.IMAGE_FILES=Image files
+service.gui.avatar.imagepicker.IMAGE_PICKER=Image picker
+service.gui.avatar.imagepicker.IMAGE_SIZE=Image size
+service.gui.avatar.imagepicker.INITIALIZING=Initializing
+service.gui.avatar.imagepicker.RESET=Reset
+service.gui.avatar.imagepicker.SET=Set
+service.gui.avatar.imagepicker.TAKE_PHOTO=Take a photo
+service.gui.avatar.imagepicker.WEBCAM_ERROR=Webcam error
+
+
# impl.protocol.ssh
#Contact Details Seperator(must not be part of contact data stored as persistent
# data)
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);
+ }
+ }
+}