+ * @param detailClass one of the detail classes defined in the + * ServerStoredDetails class, indicating the kind of details we're + * interested in. + *
+ * @return a java.util.Iterator over all details that are instances or
+ * descendants of the specified class.
+ */
+ public Iterator
+ * @param detailClass one of the detail classes defined in the
+ * ServerStoredDetails class, indicating the kind of details we're
+ * interested in.
+ *
+ * @return a java.util.Iterator over all details of specified class.
+ */
+ public Iterator
+ * @return a java.util.Iterator over all details currently set our account.
+ */
+ public Iterator
+ * @return a java.util.Iterator over all detail classes supported by the
+ * implementation.
+ */
+ public Iterator
+ * @param detailClass the class the support for which we'd like to
+ * determine.
+ *
+ * @return true if the underlying implementation supports setting details of
+ * this type and false otherwise.
+ */
+ public boolean isDetailClassSupported(
+ Class extends GenericDetail> detailClass)
+ {
+ List
+ * @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
+ * @param detail the detail that we'd like registered on the server.
+ *
+ * @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.
+ *
+ * @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).
+ *
+ * @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 ProtocolProviderService
+ * 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 c46861a78..7bc61f7a9 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 @@ private void connectAndLogin(SecurityAuthority authority, int reasonCode)
persistentPresence.setMessenger(messenger);
typingNotifications.setMessenger(messenger);
+ fireRegistrationStateChanged(
+ getRegistrationState(),
+ RegistrationState.REGISTERING,
+ RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
+ null);
+
try
{
messenger.login();
@@ -306,6 +312,17 @@ protected void initialize(String screenname, AccountID accountID)
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 5f33c55d0..dd8be4157 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 d980d9212..0fdd67f99 100644
--- a/src/net/java/sip/communicator/service/protocol/event/ChatRoomMessageReceivedEvent.java
+++ b/src/net/java/sip/communicator/service/protocol/event/ChatRoomMessageReceivedEvent.java
@@ -63,6 +63,11 @@ public class ChatRoomMessageReceivedEvent
*/
private final int eventType;
+ /**
+ * Some services can fill our room with message history.
+ */
+ private boolean historyMessage = false;
+
/**
* Creates a MessageReceivedEvent representing reception of the
* source message received from the specified from
@@ -138,4 +143,23 @@ public int getEventType()
{
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 9b7d7d09a..4d17686cc 100644
--- a/src/net/java/sip/communicator/util/ImageUtils.java
+++ b/src/net/java/sip/communicator/util/ImageUtils.java
@@ -259,4 +259,52 @@ public static BufferedImage getBufferedImage(URL imagePath)
}
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 d74ff7716..86db4d2b9 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
@@ -84,6 +84,31 @@ public void setImageIcon(byte[] image)
}
}
+ /**
+ * 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.
*
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 000000000..90a1ecaa7
--- /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 ButtonRepaintCallback 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 000000000..ae43b4fab
--- /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 000000000..1885bf6b6
--- /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);
+ }
+ }
+}