/*
* Copyright (c) 2008, 2009, 2010, 2011 Denis Tulskiy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with this work. If not, see <http://www.gnu.org/licenses/>.
*/
package com.tulskiy.musique.gui.library;
import com.sun.java.swing.Painter;
import com.tulskiy.musique.audio.player.Player;
import com.tulskiy.musique.library.MappedTreeNode;
import com.tulskiy.musique.library.TrackNode;
import com.tulskiy.musique.playlist.Playlist;
import com.tulskiy.musique.playlist.PlaylistManager;
import com.tulskiy.musique.playlist.Track;
import com.tulskiy.musique.system.Application;
import com.tulskiy.musique.system.configuration.Configuration;
import com.tulskiy.musique.util.Util;
import javax.swing.*;
import javax.swing.plaf.TreeUI;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.plaf.metal.MetalTreeUI;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import static com.tulskiy.musique.gui.library.LibraryAction.*;
/**
* Author: Denis Tulskiy
* Date: 1/4/11
*/
public class LibraryTree extends JTree {
private Application app = Application.getInstance();
private Configuration config = app.getConfiguration();
private PlaylistManager playlistManager = app.getPlaylistManager();
private Player player = app.getPlayer();
public LibraryTree() {
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
setToggleClickCount(0);
setShowsRootHandles(false);
buildListeners();
putClientProperty("JTree.lineStyle", "None");
updateUI();
}
private void buildListeners() {
addMouseListener(new ExpandListener());
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
LibraryAction action = config.getEnum("library.doubleClickAction", SEND_TO_CURRENT);
getActionMap().get(action).actionPerformed(null);
}
if (e.getButton() == MouseEvent.BUTTON2) {
if (selectRowAt(e.getPoint())) {
LibraryAction action = config.getEnum("library.middleClickAction", SEND_TO_CURRENT);
getActionMap().get(action).actionPerformed(null);
}
}
}
});
final ActionMap aMap = getActionMap();
aMap.put(SEND_TO_CURRENT, new AbstractAction(SEND_TO_CURRENT.getName()) {
@Override
public void actionPerformed(ActionEvent e) {
sendToPlaylist(playlistManager.getVisiblePlaylist());
}
});
aMap.put(SEND_TO_NEW, new AbstractAction(SEND_TO_NEW.getName()) {
@Override
public void actionPerformed(ActionEvent e) {
Playlist playlist = playlistManager.addPlaylist(getSelectionPath().getLastPathComponent().toString());
sendToPlaylist(playlist);
}
});
aMap.put(ADD_TO_CURRENT, new AbstractAction(ADD_TO_CURRENT.getName()) {
@Override
public void actionPerformed(ActionEvent e) {
Playlist playlist = playlistManager.getVisiblePlaylist();
playlist.addAll(getSelectedTracks(true));
playlist.firePlaylistChanged();
}
});
aMap.put(EXPAND_COLLAPSE, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
TreePath path = getSelectionPath();
if (isExpanded(path))
collapsePath(path);
else
expandPath(path);
}
});
InputMap iMap = getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
iMap.put(SEND_TO_CURRENT.getKeyStroke(), SEND_TO_CURRENT);
iMap.put(SEND_TO_NEW.getKeyStroke(), SEND_TO_NEW);
iMap.put(ADD_TO_CURRENT.getKeyStroke(), ADD_TO_CURRENT);
}
public boolean selectRowAt(Point p) {
int row = getClosestRowForLocation(p.x, p.y);
if (row != -1) {
setSelectionRow(row);
return true;
}
return false;
}
private void sendToPlaylist(Playlist playlist) {
ArrayList<Track> tracks = getSelectedTracks(true);
if (tracks.size() > 0) {
playlist.clear();
playlist.addAll(tracks);
player.open(playlist.get(0));
playlistManager.setActivePlaylist(playlist);
playlist.firePlaylistChanged();
}
}
@SuppressWarnings({"unchecked"})
@Override
public void updateUI() {
super.updateUI();
if (config == null)
return;
if (!Util.isNimbusLaF()) {
MetalTreeUI newUI = new MetalTreeUI() {
@Override
protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
if (tree.isRowSelected(row)) {
Color bgColor;
bgColor = ((DefaultTreeCellRenderer) currentCellRenderer).getBackgroundSelectionColor();
g.setColor(bgColor);
g.fillRect(clipBounds.x, bounds.y, clipBounds.width, bounds.height);
}
super.paintRow(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf)) {
paintExpandControl(g, clipBounds, insets, bounds,
path, row, isExpanded,
hasBeenExpanded, isLeaf);
}
}
};
setUI(newUI);
}
DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
renderer.setClosedIcon(null);
renderer.setOpenIcon(null);
renderer.setLeafIcon(null);
setCellRenderer(renderer);
UIDefaults defaults = new UIDefaults();
Color text = config.getColor("gui.color.text", null);
if (text != null) {
renderer.setTextNonSelectionColor(text);
}
setForeground(renderer.getTextNonSelectionColor());
Color background = config.getColor("gui.color.background", null);
if (background != null) {
renderer.setBackgroundNonSelectionColor(background);
}
setBackground(renderer.getBackgroundNonSelectionColor());
final Color selection = config.getColor("gui.color.selection", null);
if (selection != null) {
renderer.setBackgroundSelectionColor(selection);
renderer.setTextSelectionColor(Util.getContrastColor(selection));
defaults.put("Tree.selectionBackground", selection);
defaults.put("Tree:TreeCell[Enabled+Selected].backgroundPainter", new SelectionBackgroundPainter(selection));
}
Font font = config.getFont("gui.font.default", null);
if (font != null) {
setFont(font);
}
setRowHeight(getFont().getSize() + 10);
renderer.setBorderSelectionColor(renderer.getBackgroundSelectionColor());
Painter collapsedIconPainter = new Painter() {
@Override
public void paint(Graphics2D g, Object object, int width, int height) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(getForeground());
g.fillPolygon(
new int[]{0, (int) (height * Math.sqrt(0.75)), 0},
new int[]{0, height / 2, height},
3
);
}
};
Painter expandedIconPainter = new Painter() {
@Override
public void paint(Graphics2D g, Object object, int width, int height) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(getForeground());
g.fillPolygon(
new int[]{0, height, height / 2},
new int[]{0, 0, (int) (height * Math.sqrt(0.75))},
3
);
}
};
defaults.put("Tree[Enabled].collapsedIconPainter", collapsedIconPainter);
defaults.put("Tree[Enabled].expandedIconPainter", expandedIconPainter);
defaults.put("Tree:TreeCell[Focused+Selected].backgroundPainter", new SelectionBackgroundPainter(renderer.getBackgroundSelectionColor()));
TreeUI treeUI = getUI();
if (treeUI instanceof MetalTreeUI) {
BasicTreeUI basicUI = (BasicTreeUI) treeUI;
int size = 7;
BufferedImage expandedIcon = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
expandedIconPainter.paint(expandedIcon.createGraphics(), null, size, size);
BufferedImage collapsedIcon = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
collapsedIconPainter.paint(collapsedIcon.createGraphics(), null, size, size);
basicUI.setCollapsedIcon(new ImageIcon(collapsedIcon));
basicUI.setExpandedIcon(new ImageIcon(expandedIcon));
}
putClientProperty("Nimbus.Overrides", defaults);
putClientProperty("Nimbus.Overrides.InheritDefaults", true);
}
public void setRootNode(TreeNode rootNode) {
setModel(new DefaultTreeModel(rootNode));
revalidate();
repaint();
}
@SuppressWarnings({"unchecked"})
public ArrayList<Track> getSelectedTracks(boolean createNew) {
ArrayList<Track> tracks = new ArrayList<Track>();
TreePath[] selectionPaths = getSelectionPaths();
if (selectionPaths != null)
for (TreePath path : selectionPaths) {
MappedTreeNode node = (MappedTreeNode) path.getLastPathComponent();
List<MappedTreeNode> nodes = node.iterate();
for (MappedTreeNode treeNode : nodes) {
if (treeNode instanceof TrackNode) {
Track track = ((TrackNode) treeNode).getTrack();
if (createNew) {
track = new Track(track);
}
tracks.add(track);
}
}
}
return tracks;
}
private class SelectionBackgroundPainter implements Painter {
private final Color selection;
public SelectionBackgroundPainter(Color selection) {
this.selection = selection;
}
@Override
public void paint(Graphics2D g, Object object, int width, int height) {
g.setColor(selection);
g.fillRect(0, 0, width, height);
}
}
private class ExpandListener extends MouseAdapter {
@SuppressWarnings({"unchecked"})
@Override
public void mouseClicked(MouseEvent e) {
final int row = getClosestRowForLocation(e.getX(), e.getY());
if (row != -1) {
Rectangle bounds = getRowBounds(row);
boolean isInBounds = bounds.getX() < e.getX();
boolean isExtraSpace = bounds.getX() + bounds.getWidth() < e.getX();
if (e.getButton() == MouseEvent.BUTTON1 && isExtraSpace) {
if (e.isControlDown()) {
if (!isRowSelected(row))
addSelectionRow(row);
else
removeSelectionRow(row);
} else if (e.isShiftDown()) {
int start = getSelectionModel().getLeadSelectionRow();
if (start == -1)
start = row;
if (start < row)
start = getSelectionModel().getMinSelectionRow();
setSelectionInterval(
start,
row);
} else {
setSelectionRow(row);
}
}
if (e.isPopupTrigger() && isInBounds) {
if (!isRowSelected(row)) {
setSelectionRow(row);
}
}
}
}
}
}