package org.limewire.ui.swing.mainframe;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPopupMenu;
import javax.swing.Timer;
import javax.swing.border.Border;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import org.jdesktop.application.Resource;
import org.jdesktop.swingx.JXButton;
import org.jdesktop.swingx.painter.AbstractPainter;
import org.jdesktop.swingx.painter.BusyPainter;
import org.jdesktop.swingx.painter.Painter;
import org.limewire.friend.api.FriendConnection;
import org.limewire.friend.api.FriendConnectionEvent;
import org.limewire.listener.EventBean;
import org.limewire.listener.EventListener;
import org.limewire.listener.EventUtils;
import org.limewire.listener.ListenerSupport;
import org.limewire.listener.SwingEDTEvent;
import org.limewire.ui.swing.components.ActionLabel;
import org.limewire.ui.swing.components.LimeComboBox;
import org.limewire.ui.swing.components.decorators.ButtonDecorator;
import org.limewire.ui.swing.components.decorators.ComboBoxDecorator;
import org.limewire.ui.swing.friends.actions.BrowseFriendsAction;
import org.limewire.ui.swing.friends.actions.FriendServiceItem;
import org.limewire.ui.swing.friends.actions.LoginAction;
import org.limewire.ui.swing.friends.actions.LogoutAction;
import org.limewire.ui.swing.friends.login.AutoLoginService;
import org.limewire.ui.swing.friends.refresh.AllFriendsRefreshManager;
import org.limewire.ui.swing.friends.refresh.BrowseRefreshStatus;
import org.limewire.ui.swing.friends.refresh.BrowseRefreshStatusListener;
import org.limewire.ui.swing.listener.ActionHandListener;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;
import org.limewire.ui.swing.util.PainterUtils;
import org.limewire.ui.swing.util.SwingUtils;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class FriendsButton extends LimeComboBox {
@Resource private Icon friendOnlineIcon;
@Resource private Icon friendOfflineIcon;
@Resource private Icon friendOnlineSelectedIcon;
@Resource private Icon friendOfflineSelectedIcon;
@Resource private Icon friendLoadingIcon;
@Resource private Icon friendLoadingSelectedIcon;
@Resource private Icon friendNewFilesIcon;
@Resource private Icon friendNewFilesSelectedIcon;
@Resource private Color borderForeground = PainterUtils.TRANSPARENT;
@Resource private Color borderInsideRightForeground = PainterUtils.TRANSPARENT;
@Resource private Color borderInsideBottomForeground = PainterUtils.TRANSPARENT;
@Resource private Color dividerForeground = PainterUtils.TRANSPARENT;
@Resource private Font menuFont;
@Resource private Color menuForeground;
@Resource private Font browseNotificationFont;
@Resource private Icon browseNotificationIcon;
private boolean newResultsAvailable = false;
private final BusyPainter busyPainter;
private Timer busy;
private final JPopupMenu menu;
private final Provider<FriendServiceItem> serviceItemProvider;
private final Provider<BrowseFriendsAction> browseFriendsActionProvider;
private final Provider<LoginAction> loginActionProvider;
private final Provider<LogoutAction> logoutActionProvider;
private final AutoLoginService autoLoginService;
private final EventBean<FriendConnectionEvent> friendConnectionEventBean;
private final AllFriendsRefreshManager allFriendsRefreshManager;
@Inject
public FriendsButton(ComboBoxDecorator comboBoxDecorator,
Provider<FriendServiceItem> serviceItemProvider,
Provider<BrowseFriendsAction> browseFriendsActionProvider,
Provider<LoginAction> loginActionProvider,
Provider<LogoutAction> logoutActionProvider,
AutoLoginService autoLoginService,
EventBean<FriendConnectionEvent> friendConnectionEventBean,
AllFriendsRefreshManager allFriendsRefreshManager,
ButtonDecorator decorator) {
this.serviceItemProvider = serviceItemProvider;
this.browseFriendsActionProvider = browseFriendsActionProvider;
this.loginActionProvider = loginActionProvider;
this.logoutActionProvider = logoutActionProvider;
this.autoLoginService = autoLoginService;
this.friendConnectionEventBean = friendConnectionEventBean;
this.allFriendsRefreshManager = allFriendsRefreshManager;
GuiUtils.assignResources(this);
setText(I18n.tr("Friends"));
decorator.decorateDropDownHeaderButton(this);
busyPainter = new BusyPainter() {
@Override
protected void init(Shape point, Shape trajectory, Color b, Color h) {
super.init(getScaledDefaultPoint(8),
getScaledDefaultTrajectory(8),
Color.decode("#acacac"), Color.decode("#545454"));
}
};
final Painter<JXButton> originalPainter = getForegroundPainter();
setForegroundPainter(new AbstractPainter<JXButton>() {
@Override
protected void doPaint(Graphics2D g, JXButton object, int width, int height) {
originalPainter.paint(g, object, width, height);
// the width/heigth offset is very specific to the loading img.
if(busy != null) {
g.translate(26, 16);
busyPainter.paint(g, object, width, height);
g.translate(-26, -26);
}
}
});
addMouseListener(new ActionHandListener());
menu = new JPopupMenu();
overrideMenuNoRestyle(menu);
menu.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
menu.removeAll();
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
menu.removeAll();
}
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
populateMenu();
}
});
menu.setBorder(new Border() {
@Override
public Insets getBorderInsets(Component c) {
return new Insets(1,1,3,2);
}
@Override
public boolean isBorderOpaque() {
return false;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
JComponent serviceItem = (JComponent) ((JComponent) menu.getComponent(0)).getComponent(0);
if (!serviceItem.isVisible()) {
g.setColor(dividerForeground);
}
else {
g.setColor(serviceItem.getBackground());
}
int dividerWidth = FriendsButton.this.getWidth()-2;
g.drawLine(0, 0, dividerWidth, 0);
g.setColor(borderForeground);
g.drawLine(0, 0, 0, height-1);
g.drawLine(0, height-1, width-1, height-1);
g.drawLine(width-1, height-1, width-1, 0);
g.drawLine(dividerWidth+1, 0, width-1, 0);
g.setColor(borderInsideRightForeground);
g.drawLine(width-2, height-2, width-2, 1);
g.drawLine(width-1, height-1, width-1, height-1);
g.setColor(borderInsideBottomForeground);
g.drawLine(1, height-2, width-2, height-2);
g.drawLine(0, height-1, 0, height-1);
}
});
setPopupPosition(new Point(0, -1));
}
private void populateMenu() {
menu.add(wrapItemForSelection(serviceItemProvider.get()));
FriendConnection friendConnection = EventUtils.getSource(friendConnectionEventBean);
boolean signedIn = friendConnection != null && friendConnection.isLoggedIn();
boolean loggingIn = autoLoginService.isAttemptingLogin()
|| (friendConnection != null && friendConnection.isLoggingIn());
JLabel browseFriendMenuItem;
if (signedIn) {
browseFriendMenuItem = new ActionLabel(browseFriendsActionProvider.get());
}
else {
browseFriendMenuItem = new JLabel(I18n.tr(BrowseFriendsAction.DISPLAY_TEXT));
browseFriendMenuItem.setEnabled(false);
}
menu.add(decorateItem(browseFriendMenuItem));
if (newResultsAvailable) {
browseFriendMenuItem.setFont(browseNotificationFont);
browseFriendMenuItem.setIcon(browseNotificationIcon);
browseFriendMenuItem.setIconTextGap(2);
}
if (signedIn) {
menu.add(decorateItem(new ActionLabel(logoutActionProvider.get())));
} else {
JLabel loginMenuItem;
if (loggingIn) {
loginMenuItem = new JLabel(I18n.tr(LoginAction.DISPLAY_TEXT));
loginMenuItem.setEnabled(false);
}
else {
loginMenuItem = new ActionLabel(loginActionProvider.get());
}
menu.add(decorateItem(loginMenuItem));
}
}
private JComponent decorateItem(JComponent comp) {
attachListeners(comp);
comp.setFont(menuFont);
comp.setBorder(BorderFactory.createEmptyBorder(2, 6, 2, 6));
// Vista uses overriden foreground even in disabled state
if (comp.isEnabled()) {
comp.setForeground(menuForeground);
}
return wrapItemForSelection(comp);
}
private void setIconFromEvent(FriendConnectionEvent event) {
FriendConnectionEvent.Type eventType = event == null ? null : event.getType();
if(eventType == null) {
eventType = FriendConnectionEvent.Type.DISCONNECTED;
}
updateIcons(eventType);
}
private void updateIcons(FriendConnectionEvent.Type eventType) {
switch(eventType) {
case CONNECT_FAILED:
case DISCONNECTED:
newResultsAvailable = false;
setIcon(friendOfflineIcon);
setPressedIcon(friendOfflineSelectedIcon);
stopAnimation();
break;
case CONNECTED:
if(newResultsAvailable) {
setIcon(friendNewFilesIcon);
setPressedIcon(friendNewFilesSelectedIcon);
} else {
setIcon(friendOnlineIcon);
setPressedIcon(friendOnlineSelectedIcon);
}
stopAnimation();
break;
case CONNECTING:
setIcon(friendLoadingIcon);
setPressedIcon(friendLoadingSelectedIcon);
startAnimation();
}
}
@Inject
void register(final EventBean<FriendConnectionEvent> connectBean, ListenerSupport<FriendConnectionEvent> connectionSupport) {
setIconFromEvent(connectBean.getLastEvent());
//update icon as connection events come in.
connectionSupport.addListener(new EventListener<FriendConnectionEvent>() {
@Override
@SwingEDTEvent
public void handleEvent(FriendConnectionEvent event) {
setIconFromEvent(event);
refreshMenu();
}
});
//change to new files icon when inserts are detected
allFriendsRefreshManager.addBrowseRefreshStatusListener(new BrowseRefreshStatusListener(){
@Override
public void statusChanged(BrowseRefreshStatus status) {
if (status == BrowseRefreshStatus.ADDED || status == BrowseRefreshStatus.CHANGED){
newResultsAvailable = true;
SwingUtils.invokeNowOrLater(new Runnable() {
@Override
public void run() {
updateIcons(FriendConnectionEvent.Type.CONNECTED);
refreshMenu();
}
});
} else if (status == BrowseRefreshStatus.REFRESHED){
newResultsAvailable = false;
if (connectBean.getLastEvent().getType() == FriendConnectionEvent.Type.CONNECTED) {
SwingUtils.invokeNowOrLater(new Runnable() {
@Override
public void run() {
//Possible to refresh when offline.
updateIcons(FriendConnectionEvent.Type.CONNECTED);
}
});
}
} else if (status == BrowseRefreshStatus.REMOVED){
if(!allFriendsRefreshManager.hasSharedFiles()){
newResultsAvailable = false;
if(connectBean.getLastEvent().getType() == FriendConnectionEvent.Type.CONNECTED){
SwingUtils.invokeNowOrLater(new Runnable() {
@Override
public void run() {
//Only update if we are still online. Removal may have been triggered by logging off.
updateIcons(FriendConnectionEvent.Type.CONNECTED);
}
});
}
}
}
}
});
}
// animation code ripped from JXBusyLabel
private void startAnimation() {
if(busy != null) {
stopAnimation();
}
busy = new Timer(100, new ActionListener() {
int frame = busyPainter.getPoints();
public void actionPerformed(ActionEvent e) {
frame = (frame+1)%busyPainter.getPoints();
busyPainter.setFrame(frame);
repaint();
}
});
busy.start();
}
private void stopAnimation() {
if (busy != null) {
busy.stop();
busyPainter.setFrame(-1);
repaint();
busy = null;
}
}
private void refreshMenu() {
if (menu != null && menu.isVisible()) {
menu.setVisible(false);
menu.setVisible(true);
}
}
}