package com.robonobo.gui.components; import static com.robonobo.common.util.TextUtil.*; import static com.robonobo.gui.GuiUtil.*; import static com.robonobo.gui.RoboColor.*; import static javax.swing.SwingUtilities.*; import java.awt.*; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.event.*; import java.util.*; import java.util.List; import javax.swing.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdesktop.swingx.renderer.DefaultListRenderer; import com.robonobo.common.concurrent.CatchingRunnable; import com.robonobo.core.Platform; import com.robonobo.core.api.*; import com.robonobo.core.api.model.*; import com.robonobo.gui.RoboFont; import com.robonobo.gui.components.base.RLabel12; import com.robonobo.gui.components.base.RMenuItem; import com.robonobo.gui.frames.RobonoboFrame; import com.robonobo.gui.model.PlaylistListModel; import com.robonobo.gui.model.StreamTransfer; import com.robonobo.gui.panels.ContentPanel; import com.robonobo.gui.panels.LeftSidebar; import com.robonobo.gui.sheets.DeletePlaylistSheet; import com.robonobo.gui.sheets.SharePlaylistSheet; @SuppressWarnings("serial") public class PlaylistList extends LeftSidebarList implements UserListener, PlaylistListener, LoginListener { private static final int MAX_LBL_WIDTH = 170; ImageIcon playlistIcon; PopupMenu popup = new PopupMenu(); Log log = LogFactory.getLog(getClass()); public PlaylistList(LeftSidebar sideBar, RobonoboFrame frame) { super(sideBar, frame, new PlaylistListModel(frame.ctrl)); playlistIcon = createImageIcon("/icon/playlist.png"); setCellRenderer(new CellRenderer()); setName("robonobo.playlist.list"); setAlignmentX(0.0f); setMaximumSize(new Dimension(65535, 65535)); // We do the listener stuff here rather than in the model as we may need to reselect or resize as a consequence frame.ctrl.addUserListener(this); frame.ctrl.addPlaylistListener(this); frame.ctrl.addLoginListener(this); setTransferHandler(new DnDHandler()); addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { int idx = locationToIndex(e.getPoint()); if (idx != getSelectedIndex()) setSelectedIndex(idx); popup.show(e.getComponent(), e.getX(), e.getY()); } } }); } public PlaylistListModel getModel() { return (PlaylistListModel) super.getModel(); } public void selectPlaylist(final Playlist p) { invokeLater(new CatchingRunnable() { public void doRun() throws Exception { setSelectedIndex(getModel().getPlaylistIndex(p)); } }); } @Override public void loginSucceeded(User me) { runOnUiThread(new CatchingRunnable() { public void doRun() throws Exception { getModel().clear(); } }); } @Override public void loginFailed(String reason) { // Do nothing } @Override public String getToolTipText(MouseEvent event) { int idx = locationToIndex(event.getPoint()); if(idx < 0) return null; PlaylistListModel m = getModel(); int unseen = m.numUnseen(idx); Playlist p = m.getPlaylistAt(idx); boolean hasCmts = m.hasComments(p.getPlaylistId()); if(unseen > 0) { String nt = numItems(unseen, "new track"); if(hasCmts) { // html allows us to have linebreaks return "<html>"+nt+"<br>Unread comments</html>"; } else return nt; } if(hasCmts) return "Unread comments"; return null; } @Override public void userChanged(final User u) { if (u.getUserId() != frame.ctrl.getMyUser().getUserId()) return; runOnUiThread(new CatchingRunnable() { public void doRun() throws Exception { Playlist selP = selectedPlaylist(); boolean selPGone = false; // Check for removed playlists List<Playlist> toRm = new ArrayList<Playlist>(); for (Playlist p : getModel()) { if (!u.getPlaylistIds().contains(p.getPlaylistId())) toRm.add(p); } for (Playlist p : toRm) { if (p.equals(selP)) selPGone = true; getModel().remove(p); } // Removing items might have buggered up the selection, so put it back. // If the selected playlist has gone, then go to my library if (selP != null) { if (selPGone) frame.leftSidebar.selectMyMusic(); else { int idx = getModel().getPlaylistIndex(selP); setSelectedIndex(idx); } } } }); } @Override public void playlistChanged(final Playlist p) { User me = frame.ctrl.getMyUser(); if (p.getOwnerIds().contains(me.getUserId())) { // Don't include special playlists, they're handled separately if(frame.ctrl.isSpecialPlaylist(p.getTitle())) return; Long plId = p.getPlaylistId(); Set<Long> myPlIds = frame.ctrl.getMyUser().getPlaylistIds(); if (!myPlIds.contains(plId)) log.error("Error updating playlist: playlist says it's mine, but my playlist ids do not contain it"); runOnUiThread(new CatchingRunnable() { public void doRun() throws Exception { Playlist selP = selectedPlaylist(); boolean needReselect = p.equals(selP); getModel().insertSorted(p); if (needReselect) { int idx = getModel().getPlaylistIndex(p); setSelectedIndex(idx); } revalidate(); } }); } } @Override public void gotPlaylistComments(final long plId, boolean anyUnseen, Map<Comment, Boolean> comments) { if (!anyUnseen) { return; } // If this playlist is selected and the comments tab is showing, comments have already been seen Playlist selP = selectedPlaylist(); if(selP != null && selP.getPlaylistId() == plId) { ContentPanel cp = frame.mainPanel.getContentPanel("playlist/"+plId); if(cp.tabPane.getSelectedIndex() == 1) return; } runOnUiThread(new CatchingRunnable() { public void doRun() throws Exception { final PlaylistListModel m = getModel(); if (!m.hasPlaylist(plId)) { return; } m.setHasComments(plId, true); } }); } public void markPlaylistCommentsAsRead(long plId) { PlaylistListModel m = getModel(); m.setHasComments(plId, false); } @Override public void userConfigChanged(UserConfig cfg) { // Do nothing } @Override protected void itemSelected(int index) { PlaylistListModel m = getModel(); final Playlist p = m.getPlaylistAt(index); final long plId = p.getPlaylistId(); String pnlName = "playlist/" + plId; frame.mainPanel.selectContentPanel(pnlName); int unseen = m.numUnseen(index); if (unseen > 0) { m.markAllAsSeen(index); frame.ctrl.getExecutor().execute(new CatchingRunnable() { public void doRun() throws Exception { frame.ctrl.markAllAsSeen(p); } }); } // If the panel's comments tab is showing, mark comments as read ContentPanel cp = frame.mainPanel.getContentPanel(pnlName); if(cp.tabPane.getSelectedIndex() == 1) markPlaylistCommentsAsRead(plId); } private Playlist selectedPlaylist() { return (getSelectedIndex() < 0) ? null : getModel().getPlaylistAt(getSelectedIndex()); } class PopupMenu extends JPopupMenu implements ActionListener { public PopupMenu() { addItem("Post to facebook...", "fb"); addItem("Post to twitter...", "twit"); addItem("Share...", "share"); addItem("Delete", "del"); } private void addItem(String text, String cmd) { RMenuItem rmi = new RMenuItem(text); rmi.setActionCommand(cmd); rmi.addActionListener(this); add(rmi); } public void actionPerformed(ActionEvent e) { int selIdx = getSelectedIndex(); if (selIdx < 0) return; Playlist p = getModel().getPlaylistAt(selIdx); String action = e.getActionCommand(); if (action.equals("fb")) frame.postToFacebook(p); else if (action.equals("twit")) frame.postToTwitter(p); else if (action.equals("share")) { SharePlaylistSheet sps = new SharePlaylistSheet(frame, p); frame.showSheet(sps); } else if (action.equals("del")) { DeletePlaylistSheet dps = new DeletePlaylistSheet(frame, p); frame.showSheet(dps); } } } class ItemLbl extends RLabel12 { public ItemLbl() { setOpaque(true); setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); setMaximumSize(new Dimension(MAX_LBL_WIDTH, 20)); setPreferredSize(new Dimension(MAX_LBL_WIDTH, 20)); } } class CellRenderer extends DefaultListRenderer { JLabel lbl = new ItemLbl(); Font normalFont, boldFont; public CellRenderer() { normalFont = RoboFont.getFont(12, false); boldFont = RoboFont.getFont(12, true); } @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { String text = (String) value; PlaylistListModel m = getModel(); int unseen = m.numUnseen(index); Playlist p = m.getPlaylistAt(index); boolean hasCmts = m.hasComments(p.getPlaylistId()); boolean useBold = false; boolean useRed = false; if (unseen > 0) { text = text + "[" + unseen + "]"; useBold = true; } if (hasCmts) { useBold = true; useRed = true; } if (useBold) lbl.setFont(boldFont); else lbl.setFont(normalFont); lbl.setText(text); lbl.setIcon(playlistIcon); if (isSelected) { lbl.setBackground(LIGHT_GRAY); lbl.setForeground(BLUE_GRAY); } else { lbl.setBackground(MID_GRAY); lbl.setForeground(DARK_GRAY); } if (useRed) lbl.setForeground(GREEN); return lbl; } } class DnDHandler extends TransferHandler { @Override public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { for (DataFlavor dataFlavor : transferFlavors) { if (dataFlavor.equals(StreamTransfer.DATA_FLAVOR)) return true; } return Platform.getPlatform().canDnDImport(transferFlavors); } @Override public boolean importData(JComponent comp, Transferable t) { Playlist p = getModel().getPlaylistAt(getSelectedIndex()); String cpName = "playlist/" + p.getPlaylistId(); ContentPanel cp = frame.mainPanel.getContentPanel(cpName); return cp.importData(comp, t); } } @Override public void allUsersAndPlaylistsLoaded() { } }