Author: pwang Date: 2009-03-16 15:30:19 -0700 (Mon, 16 Mar 2009) New Revision: 16288
Added: csplugins/trunk/ucsd/pwang/cyprovision/org.eclipse.equinox.p2.ui2/src/org/eclipse/equinox/internal/provisional/p2/ui2/dialogs/CheckBoxTreeCellRenderer.java Log: free software from http://furbelow.sf.net Added: csplugins/trunk/ucsd/pwang/cyprovision/org.eclipse.equinox.p2.ui2/src/org/eclipse/equinox/internal/provisional/p2/ui2/dialogs/CheckBoxTreeCellRenderer.java =================================================================== --- csplugins/trunk/ucsd/pwang/cyprovision/org.eclipse.equinox.p2.ui2/src/org/eclipse/equinox/internal/provisional/p2/ui2/dialogs/CheckBoxTreeCellRenderer.java 2009-03-16 20:27:27 UTC (rev 16287) +++ csplugins/trunk/ucsd/pwang/cyprovision/org.eclipse.equinox.p2.ui2/src/org/eclipse/equinox/internal/provisional/p2/ui2/dialogs/CheckBoxTreeCellRenderer.java 2009-03-16 22:30:19 UTC (rev 16288) @@ -0,0 +1,482 @@ +package org.eclipse.equinox.internal.provisional.p2.ui2.dialogs; + +//package furbelow; +/* Copyright (c) 2006-2007 Timothy Wall, All Rights Reserved +* +* This library 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 2.1 of the License, or (at your option) any later version. +* <p/> +* This library 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. +*/ +import java.awt.BasicStroke; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.ComponentOrientation; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import javax.swing.AbstractAction; +import javax.swing.Icon; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTree; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreePath; + +/** Provides checkbox-based selection of tree nodes. Override the protected + * methods to adapt this renderer's behavior to your local tree table flavor. + * No change listener notifications are provided. + */ +public class CheckBoxTreeCellRenderer implements TreeCellRenderer { + + public static final int UNCHECKABLE = 0; + public static final int FULLCHECKED = 1; + public static final int UNCHECKED = 2; + public static final int PARTIALCHECKED = 3; + + private TreeCellRenderer renderer; + private JCheckBox checkBox; + private Point mouseLocation; + private int mouseRow = -1; + private int pressedRow = -1; + private boolean mouseInCheck; + private int state = UNCHECKED; + private Set checkedPaths; + private JTree tree; + private MouseHandler handler; + + /** Create a per-tree instance of the checkbox renderer. */ + public CheckBoxTreeCellRenderer(JTree tree, TreeCellRenderer original) { + this.tree = tree; + this.renderer = original; + checkedPaths = new HashSet(); + checkBox = new JCheckBox(); + checkBox.setOpaque(false); + checkBox.setSize(checkBox.getPreferredSize()); + } + + protected void installMouseHandler() { + if (handler == null) { + handler = new MouseHandler(); + addMouseHandler(handler); + } + } + + protected void addMouseHandler(MouseHandler handler) { + tree.addMouseListener(handler); + tree.addMouseMotionListener(handler); + } + + private void updateMouseLocation(Point newLoc) { + if (mouseRow != -1) { + repaint(mouseRow); + } + mouseLocation = newLoc; + if (mouseLocation != null) { + mouseRow = getRow(newLoc); + repaint(mouseRow); + } + else { + mouseRow = -1; + } + if (mouseRow != -1 && mouseLocation != null) { + Point mouseLoc = new Point(mouseLocation); + Rectangle r = getRowBounds(mouseRow); + if (r != null) + mouseLoc.x -= r.x; + mouseInCheck = isInCheckBox(mouseLoc); + } + else { + mouseInCheck = false; + } + } + + protected int getRow(Point p) { + return tree.getRowForLocation(p.x, p.y); + } + + protected Rectangle getRowBounds(int row) { + return tree.getRowBounds(row); + } + + protected TreePath getPathForRow(int row) { + return tree.getPathForRow(row); + } + + protected int getRowForPath(TreePath path) { + return tree.getRowForPath(path); + } + + protected void repaint(Rectangle r) { + tree.repaint(r); + } + + protected void repaint() { + tree.repaint(); + } + + private void repaint(int row) { + Rectangle r = getRowBounds(row); + if (r != null) + repaint(r); + } + + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + installMouseHandler(); + TreePath path = getPathForRow(row); + state = UNCHECKABLE; + if (path != null) { + if (isChecked(path)) { + state = FULLCHECKED; + } + else if (isPartiallyChecked(path)) { + state = PARTIALCHECKED; + } + else if (isSelectable(path)) { + state = UNCHECKED; + } + } + checkBox.setSelected(state == FULLCHECKED); + checkBox.getModel().setArmed(mouseRow == row && pressedRow == row && mouseInCheck); + checkBox.getModel().setPressed(pressedRow == row && mouseInCheck); + checkBox.getModel().setRollover(mouseRow == row && mouseInCheck); + + Component c = renderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + checkBox.setForeground(c.getForeground()); + if (c instanceof JLabel) { + JLabel label = (JLabel)c; + // Augment the icon to include the checkbox + label.setIcon(new CompoundIcon(label.getIcon())); + } + return c; + } + + private boolean isInCheckBox(Point where) { + Insets insets = tree.getInsets(); + int right = checkBox.getWidth(); + int left = 0; + if (insets != null) { + left += insets.left; + right += insets.left; + } + return where.x >= left && where.x < right; + } + + public boolean isExplicitlyChecked(TreePath path) { + return checkedPaths.contains(path); + } + + /** Returns whether selecting the given path is allowed. The default + * returns true. You should return false if the given path represents + * a placeholder for a node that has not yet loaded, or anything else + * that doesn't represent a normal, operable object in the tree. + */ + public boolean isSelectable(TreePath path) { + return true; + } + + /** Returns whether the given path is currently checked. */ + public boolean isChecked(TreePath path) { + if (isExplicitlyChecked(path)) { + return true; + } + else { + if (path.getParentPath() != null) { + return isChecked(path.getParentPath()); + } + else { + return false; + } + } + } + + public boolean isPartiallyChecked(TreePath path) { + Object node = path.getLastPathComponent(); + for (int i = 0; i < tree.getModel().getChildCount(node); i++) { + Object child = tree.getModel().getChild(node, i); + TreePath childPath = path.pathByAddingChild(child); + if (isChecked(childPath) || isPartiallyChecked(childPath)) { + return true; + } + } + return false; + } + + private boolean isFullyChecked(TreePath parent) { + Object node = parent.getLastPathComponent(); + for (int i = 0; i < tree.getModel().getChildCount(node); i++) { + Object child = tree.getModel().getChild(node, i); + TreePath childPath = parent.pathByAddingChild(child); + if (!isExplicitlyChecked(childPath)) { + return false; + } + } + return true; + } + + public void toggleChecked(int row) { + TreePath path = getPathForRow(row); + boolean isChecked = isChecked(path); + removeDescendants(path); + if (!isChecked) { + checkedPaths.add(path); + } + setParent(path); + repaint(); + } + + private void setParent(TreePath path) { + TreePath parent = path.getParentPath(); + if (parent != null) { + if (isFullyChecked(parent)) { + removeChildren(parent); + checkedPaths.add(parent); + } else { + if (isChecked(parent)) { + checkedPaths.remove(parent); + addChildren(parent); + checkedPaths.remove(path); + } + } + setParent(parent); + } + } + + private void addChildren(TreePath parent) { + Object node = parent.getLastPathComponent(); + for (int i = 0; i < tree.getModel().getChildCount(node); i++) { + Object child = tree.getModel().getChild(node, i); + TreePath path = parent.pathByAddingChild(child); + checkedPaths.add(path); + } + } + + private void removeChildren(TreePath parent) { + for (Iterator i = checkedPaths.iterator(); i.hasNext();) { + TreePath p = (TreePath) i.next(); + if (p.getParentPath() != null && parent.equals(p.getParentPath())) { + i.remove(); + } + } + } + + private void removeDescendants(TreePath ancestor) { + for (Iterator i = checkedPaths.iterator(); i.hasNext();) { + TreePath path = (TreePath) i.next(); + if (ancestor.isDescendant(path)) { + i.remove(); + } + } + } + + /** Returns all checked rows. */ + public int[] getCheckedRows() { + TreePath[] paths = getCheckedPaths(); + int[] rows = new int[checkedPaths.size()]; + for (int i = 0; i < checkedPaths.size(); i++) { + rows[i] = getRowForPath(paths[i]); + } + Arrays.sort(rows); + return rows; + } + + /** Returns all checked paths. */ + public TreePath[] getCheckedPaths() { + return (TreePath[]) checkedPaths.toArray(new TreePath[checkedPaths.size()]); + } + + protected class MouseHandler extends MouseAdapter implements MouseMotionListener { + public void mouseEntered(MouseEvent e) { + updateMouseLocation(e.getPoint()); + } + public void mouseExited(MouseEvent e) { + updateMouseLocation(null); + } + public void mouseMoved(MouseEvent e) { + updateMouseLocation(e.getPoint()); + } + public void mouseDragged(MouseEvent e) { + updateMouseLocation(e.getPoint()); + } + public void mousePressed(MouseEvent e) { + pressedRow = e.getModifiersEx() == InputEvent.BUTTON1_DOWN_MASK + ? getRow(e.getPoint()) : -1; + updateMouseLocation(e.getPoint()); + } + public void mouseReleased(MouseEvent e) { + if (pressedRow != -1) { + int row = getRow(e.getPoint()); + if (row == pressedRow) { + Point p = e.getPoint(); + Rectangle r = getRowBounds(row); + p.x -= r.x; + if (isInCheckBox(p)) { + toggleChecked(row); + } + } + pressedRow = -1; + updateMouseLocation(e.getPoint()); + } + } + } + + /** Combine a JCheckBox's checkbox with another icon. */ + private final class CompoundIcon implements Icon { + private final Icon icon; + private final int w; + private final int h; + + private CompoundIcon(Icon icon) { + if (icon == null) { + icon = new Icon() { + public int getIconHeight() { return 0; } + public int getIconWidth() { return 0; } + public void paintIcon(Component c, Graphics g, int x, int y) { } + }; + } + this.icon = icon; + this.w = icon.getIconWidth(); + this.h = icon.getIconHeight(); + } + + public int getIconWidth() { + return checkBox.getPreferredSize().width + w; + } + + public int getIconHeight() { + return Math.max(checkBox.getPreferredSize().height, h); + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + if (c.getComponentOrientation().isLeftToRight()) { + int xoffset = checkBox.getPreferredSize().width; + int yoffset = (getIconHeight()-icon.getIconHeight())/2; + icon.paintIcon(c, g, x + xoffset, y + yoffset); + if (state != UNCHECKABLE) { + paintCheckBox(g, x, y); + } + } + else { + int yoffset = (getIconHeight()-icon.getIconHeight())/2; + icon.paintIcon(c, g, x, y + yoffset); + if (state != UNCHECKABLE) { + paintCheckBox(g, x + icon.getIconWidth(), y); + } + } + } + + private void paintCheckBox(Graphics g, int x, int y) { + int yoffset; + boolean db = checkBox.isDoubleBuffered(); + checkBox.setDoubleBuffered(false); + try { + yoffset = (getIconHeight()-checkBox.getPreferredSize().height)/2; + g = g.create(x, y+yoffset, getIconWidth(), getIconHeight()); + checkBox.paint(g); + if (state == PARTIALCHECKED) { + final int WIDTH = 2; + g.setColor(UIManager.getColor("CheckBox.foreground")); + Graphics2D g2d = (Graphics2D)g; + g2d.setStroke(new BasicStroke(WIDTH, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + int w = checkBox.getWidth(); + int h = checkBox.getHeight(); + g.drawLine(w/4+2, h/2-WIDTH/2+1, w/4+w/2-3, h/2-WIDTH/2+1); + } + g.dispose(); + } + finally { + checkBox.setDoubleBuffered(db); + } + } + } + + private static String createText(TreePath[] paths) { + if (paths.length == 0) { + return "Nothing checked"; + } + String checked = "Checked:\n"; + for (int i=0;i < paths.length;i++) { + checked += paths[i] + "\n"; + } + return checked; + } + + public static void main(String[] args) { + try { + final String SWITCH = "toggle-componentOrientation"; + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + JFrame frame = new JFrame("Tree with Check Boxes"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + final JTree tree = new JTree(); + final CheckBoxTreeCellRenderer r = + new CheckBoxTreeCellRenderer(tree, tree.getCellRenderer()); + tree.setCellRenderer(r); + tree.getActionMap().put(SWITCH, new AbstractAction(SWITCH) { + public void actionPerformed(ActionEvent e) { + ComponentOrientation o = tree.getComponentOrientation(); + if (o.isLeftToRight()) { + o = ComponentOrientation.RIGHT_TO_LEFT; + } + else { + o = ComponentOrientation.LEFT_TO_RIGHT; + } + tree.setComponentOrientation(o); + tree.repaint(); + } + }); + int mask = InputEvent.SHIFT_MASK|Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_O, mask), SWITCH); + + final JTextArea text = new JTextArea(createText(r.getCheckedPaths())); + text.setPreferredSize(new Dimension(200, 100)); + tree.addMouseListener(new MouseAdapter() { + public void mouseReleased(MouseEvent e) { + // Invoke later to ensure all mouse handling is completed + SwingUtilities.invokeLater(new Runnable() { public void run() { + text.setText(createText(r.getCheckedPaths())); + }}); + } + }); + + frame.getContentPane().add(new JScrollPane(tree)); + frame.getContentPane().add(new JScrollPane(text), BorderLayout.SOUTH); + + frame.pack(); + frame.setSize(300, 350); + frame.setVisible(true); + } + catch(Exception e) { + e.printStackTrace(); + System.exit(1); + } + } +} --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "cytoscape-cvs" group. To post to this group, send email to [email protected] To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/cytoscape-cvs?hl=en -~----------~----~----~----~------~----~------~--~---
