/* * Copyright (c) 2008, 2009, 2010 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.dialogs; import com.sun.java.swing.Painter; import com.tulskiy.musique.images.Images; import com.tulskiy.musique.util.Util; import javax.swing.*; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.filechooser.FileSystemView; 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.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.File; import java.util.*; @SuppressWarnings({"unchecked"}) public class DirectoryChooser extends JTree { private static FileSystemView fsv = FileSystemView.getFileSystemView(); private boolean enableFiles; public DirectoryChooser(boolean enableFiles) { this(null, enableFiles); } public DirectoryChooser(File dir, boolean enableFiles) { this.enableFiles = enableFiles; setModel(new DefaultTreeModel(new DirectoryNode(fsv.getRoots()[0]))); getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); setSelectedFile(dir); addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent ev) { File oldDir = null; TreePath oldPath = ev.getOldLeadSelectionPath(); if (oldPath != null) { oldDir = ((FileNode) oldPath.getLastPathComponent()).getFile(); if (!fsv.isFileSystem(oldDir)) { oldDir = null; } } File[] files = getSelectedFiles(); File newDir = files.length > 0 ? files[0] : null; firePropertyChange("selectedDirectory", oldDir, newDir); } }); setToggleClickCount(2); putClientProperty("JTree.lineStyle", "None"); buildListeners(); } private void buildListeners() { addMouseListener(new 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); } } } } }); } public boolean isEnableFiles() { return enableFiles; } public void setEnableFiles(boolean enableFiles) { this.enableFiles = enableFiles; } public void setSelectedFile(File file) { if (file == null) { file = fsv.getDefaultDirectory(); } TreePath path; try { path = makePath(file); } catch (Exception e) { path = makePath(fsv.getDefaultDirectory()); } setSelectionPath(path); expandPath(path); int row = Math.max(0, getRowForPath(path) - 4); Rectangle rect = getRowBounds(row); rect.x = 0; rect.width = getWidth(); scrollRectToVisible(rect); } public File[] getSelectedFiles() { TreePath[] paths = getSelectionPaths(); ArrayList<File> files = new ArrayList<File>(); if (paths != null) for (TreePath path : paths) { FileNode node = (FileNode) path.getLastPathComponent(); File file = node.getFile(); if (fsv.isFileSystem(file)) { files.add(file); } } return files.toArray(new File[files.size()]); } private TreePath makePath(File dir) { DirectoryNode root = (DirectoryNode) getModel().getRoot(); if (root.getFile().equals(dir)) { return new TreePath(root); } TreePath parentPath = makePath(fsv.getParentDirectory(dir)); DirectoryNode parentNode = (DirectoryNode) parentPath.getLastPathComponent(); Enumeration enumeration = parentNode.children(); while (enumeration.hasMoreElements()) { FileNode child = (FileNode) enumeration.nextElement(); if (child.getFile().equals(dir)) { return parentPath.pathByAddingChild(child); } } return null; } public void updateUI() { super.updateUI(); 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(); if (Util.isGTKLaF()) { renderer.setClosedIcon(Images.loadIcon("directory.png")); renderer.setOpenIcon(Images.loadIcon("directory.png")); renderer.setLeafIcon(Images.loadIcon("file.png")); } setCellRenderer(renderer); setForeground(renderer.getTextNonSelectionColor()); setBackground(renderer.getBackgroundNonSelectionColor()); UIDefaults defaults = new UIDefaults(); 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); } 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); } } class FileNode extends DefaultMutableTreeNode { File file; FileNode(File file) { super(file); this.file = file; } public File getFile() { return file; } @Override public String toString() { return file.getName(); } } class DirectoryNode extends FileNode { DirectoryNode(File file) { super(file); } public int getChildCount() { populateChildren(); return super.getChildCount(); } public Enumeration children() { populateChildren(); return super.children(); } public boolean isLeaf() { return false; } public void reload() { children = null; populateChildren(); } private void populateChildren() { if (children == null) { children = new Vector(); File[] files = fsv.getFiles(file, true); Arrays.sort(files, new Comparator<File>() { @Override public int compare(File o1, File o2) { if (o1.isDirectory() ^ o2.isDirectory()) { return o1.isDirectory() ? -1 : 1; } else { return o1.compareTo(o2); } } }); for (File f : files) { if (fsv.isTraversable(f)) { insert(new DirectoryNode(f), (children == null) ? 0 : children.size()); } else if (f.isFile() && isEnableFiles()) { insert(new FileNode(f), (children == null) ? 0 : children.size()); } } } } public String toString() { return fsv.getSystemDisplayName(file); } public boolean equals(Object o) { return (o instanceof DirectoryNode && userObject.equals(((DirectoryNode) o).getUserObject())); } } }