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 java.awt.*; import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.Map; import javax.swing.*; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.*; import com.robonobo.gui.GuiUtil; import com.robonobo.gui.RoboFont; import com.robonobo.gui.frames.RobonoboFrame; import com.robonobo.gui.model.*; import com.robonobo.gui.panels.ContentPanel; import com.robonobo.gui.panels.LeftSidebar; @SuppressWarnings("serial") public class FriendTree extends LeftSidebarTree implements LeftSidebarComponent { static final Dimension MAX_LVL0_SZ = new Dimension(150, Integer.MAX_VALUE); static final Dimension MAX_LVL1_SZ = new Dimension(145, Integer.MAX_VALUE); static final Dimension MAX_LVL2_SZ = new Dimension(135, Integer.MAX_VALUE); LeftSidebar sideBar; ImageIcon rootIcon, addFriendsIcon, friendIcon, playlistIcon, libraryIcon; Map<String, ImageIcon> specIcons = new HashMap<String, ImageIcon>(); Font normalFont, boldFont; public FriendTree(LeftSidebar sb, RobonoboFrame frame) { super(new FriendTreeModel(frame), frame); this.sideBar = sb; getModel().setTree(this); normalFont = RoboFont.getFont(12, false); boldFont = RoboFont.getFont(12, true); setName("robonobo.playlist.tree"); setAlignmentX(0.0f); setRootVisible(true); collapseRow(0); rootIcon = createImageIcon("/icon/friends.png"); addFriendsIcon = createImageIcon("/icon/add_friends.png"); friendIcon = createImageIcon("/icon/friend.png"); playlistIcon = createImageIcon("/icon/playlist.png"); libraryIcon = createImageIcon("/icon/home.png"); // Special playlist icons specIcons.put("loves", createImageIcon("/icon/heart-small.png")); specIcons.put("radio", createImageIcon("/icon/radio-small.png")); setCellRenderer(new CellRenderer()); setSelectionModel(new SelectionModel()); getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); addTreeSelectionListener(new SelectionListener()); // This is required for tooltips to work ToolTipManager.sharedInstance().registerComponent(this); } @Override public FriendTreeModel getModel() { return (FriendTreeModel) super.getModel(); } public void selectForPlaylist(Long playlistId) { setSelectionPath(getModel().getPlaylistTreePath(playlistId)); } public void selectForLibrary(long userId) { setSelectionPath(getModel().getLibraryTreePath(userId)); } @Override public String getToolTipText(MouseEvent evt) { Point p = evt.getPoint(); TreePath tp = getClosestPathForLocation(p.x, p.y); Object node = tp.getLastPathComponent(); if(node instanceof PlaylistTreeNode) { PlaylistTreeNode ptn = (PlaylistTreeNode) node; int unseen = ptn.numUnseenTracks; if(unseen > 0) { String nt = numItems(unseen, "new track"); if(ptn.hasComments) { // html allows us to have linebreaks return "<html>"+nt+"<br>Unread comments</html>"; } else return nt; } if(ptn.hasComments) return "Unread comments"; } else if(node instanceof LibraryTreeNode) { LibraryTreeNode ltn = (LibraryTreeNode) node; int unseen = ltn.numUnseenTracks; if(unseen > 0) { String nt = numItems(unseen, "new track"); if(ltn.hasComments) { // html allows us to have linebreaks return "<html>"+nt+"<br>Unread comments</html>"; } else return nt; } if(ltn.hasComments) return "Unread comments"; } return null; } private class SelectionListener implements TreeSelectionListener { public void valueChanged(TreeSelectionEvent e) { TreePath tp = e.getNewLeadSelectionPath(); if (tp == null) return; Object selNode = tp.getLastPathComponent(); if (selNode instanceof ButtonTreeNode) { ButtonTreeNode btn = (ButtonTreeNode) selNode; btn.onClick(); setSelectionPath(e.getOldLeadSelectionPath()); return; } if (!(selNode instanceof SelectableTreeNode)) return; SelectableTreeNode stn = (SelectableTreeNode) selNode; if (stn.wantSelect()) { sideBar.clearSelectionExcept(FriendTree.this); // If this is a library or playlist node with comments, and the comments tab is showing, mark the // comments as read FriendTreeModel m = getModel(); if (stn instanceof LibraryTreeNode) { LibraryTreeNode ltn = (LibraryTreeNode) stn; ContentPanel cp = frame.mainPanel.getContentPanel("library/" + ltn.userId); // library content panel gets created on demand, so might be null if (cp != null && cp.tabPane.getSelectedIndex() == 1) m.markLibraryCommentsAsRead(ltn.userId); } else if (stn instanceof PlaylistTreeNode) { PlaylistTreeNode ptn = (PlaylistTreeNode) stn; long plId = ptn.getPlaylist().getPlaylistId(); m.markPlaylistTracksAsSeen(plId); ContentPanel cp = frame.mainPanel.getContentPanel("playlist/" + plId); if (cp.tabPane.getSelectedIndex() == 1) m.markPlaylistCommentsAsRead(plId); } if (stn.handleSelect()) m.firePathToRootChanged(stn); } else setSelectionPath(e.getOldLeadSelectionPath()); } } private class CellRenderer extends DefaultTreeCellRenderer { public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { final TreeNode node = (TreeNode) value; final JLabel lbl = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); boolean useBold = false; boolean useRed = false; if (node instanceof PlaylistTreeNode) { PlaylistTreeNode ptn = (PlaylistTreeNode) node; Icon specIcon = specIcons.get(ptn.getPlaylist().getTitle().toLowerCase()); if(specIcon != null) lbl.setIcon(specIcon); else lbl.setIcon(playlistIcon); lbl.setMaximumSize(MAX_LVL2_SZ); lbl.setPreferredSize(MAX_LVL2_SZ); int unseen = ptn.numUnseenTracks; if (!sel && unseen > 0) { lbl.setText("[" + unseen + "] " + lbl.getText()); useBold = true; } if (ptn.hasComments) { useRed = true; useBold = true; } } else if (node instanceof LibraryTreeNode) { lbl.setIcon(libraryIcon); lbl.setMaximumSize(MAX_LVL2_SZ); lbl.setPreferredSize(MAX_LVL2_SZ); LibraryTreeNode ltn = (LibraryTreeNode) node; int unseen = ltn.numUnseenTracks; if (!sel && unseen > 0) { lbl.setText("[" + unseen + "] " + lbl.getText()); useBold = true; } if (ltn.hasComments) { useRed = true; useBold = true; } } else if (node instanceof FriendTreeNode) { lbl.setIcon(friendIcon); lbl.setMaximumSize(MAX_LVL1_SZ); lbl.setPreferredSize(MAX_LVL1_SZ); int unseen = getTotalUnseen(node); if (!sel && unseen > 0) { lbl.setText("[" + unseen + "] " + lbl.getText()); useBold = true; } if (anyComments(node)) { useBold = true; useRed = true; } } else if (node instanceof AddFriendsTreeNode) { lbl.setIcon(addFriendsIcon); lbl.setMaximumSize(MAX_LVL1_SZ); lbl.setPreferredSize(MAX_LVL1_SZ); } else if (node.getParent() == null) { lbl.setIcon(rootIcon); lbl.setMaximumSize(MAX_LVL0_SZ); lbl.setPreferredSize(MAX_LVL0_SZ); // Are there any unseen tracks at all? int unseen = getTotalUnseen(node); if (unseen > 0) useBold = true; if (anyComments(node)) { useBold = true; useRed = true; } } if (useBold) lbl.setFont(boldFont); else lbl.setFont(normalFont); if (sel) { lbl.setForeground(BLUE_GRAY); lbl.setBackground(LIGHT_GRAY); } else { lbl.setForeground(DARK_GRAY); lbl.setBackground(MID_GRAY); } if (useRed) lbl.setForeground(GREEN); return lbl; } public void paint(Graphics g) { paintComponent(g); } @Override protected void paintComponent(Graphics g) { GuiUtil.makeTextLookLessRubbish(g); super.paintComponent(g); } int getTotalUnseen(TreeNode n) { int unseen = 0; for (int i = 0; i < n.getChildCount(); i++) { TreeNode child = n.getChildAt(i); if (child instanceof PlaylistTreeNode) unseen += ((PlaylistTreeNode) child).numUnseenTracks; else if (child instanceof LibraryTreeNode) unseen += ((LibraryTreeNode) child).numUnseenTracks; else unseen += getTotalUnseen(child); } return unseen; } boolean anyComments(TreeNode n) { for (int i = 0; i < n.getChildCount(); i++) { TreeNode child = n.getChildAt(i); if (child instanceof PlaylistTreeNode) { if (((PlaylistTreeNode) child).hasComments) return true; } else if (child instanceof LibraryTreeNode) { if (((LibraryTreeNode) child).hasComments) return true; } else { if (anyComments(child)) return true; } } return false; } } }