/******************************************************************************* | |
* Copyright (c) 2010 BSI Business Systems Integration AG. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v10.html | |
* | |
* Contributors: | |
* BSI Business Systems Integration AG - initial API and implementation | |
******************************************************************************/ | |
package org.eclipse.scout.rt.ui.swing.ext; | |
import java.awt.Color; | |
import java.awt.Dimension; | |
import java.awt.GraphicsEnvironment; | |
import java.awt.Point; | |
import java.awt.Rectangle; | |
import java.awt.dnd.DropTarget; | |
import java.awt.event.MouseEvent; | |
import java.util.Enumeration; | |
import javax.swing.BorderFactory; | |
import javax.swing.JTree; | |
import javax.swing.ToolTipManager; | |
import javax.swing.TransferHandler; | |
import javax.swing.UIManager; | |
import javax.swing.plaf.UIResource; | |
import javax.swing.tree.TreePath; | |
import javax.swing.tree.TreeSelectionModel; | |
import org.eclipse.scout.rt.ui.swing.SwingUtility; | |
import org.eclipse.scout.rt.ui.swing.dnd.DefaultDropTarget; | |
import org.eclipse.scout.rt.ui.swing.dnd.TransferHandlerEx; | |
/** | |
* Various enhancements and fixes to JTree | |
* <ul> | |
* <li>DnD handling using {@link TransferHandlerEx} and {@link DefaultDropTarget}</li> | |
* <li>Support for getPreferredContentSize</li> | |
* <li>Support for setPreferredScrollableViewportSize</li> | |
* <li>fixed setTooltipText that constantly removed tooltip manager registration</li> | |
* </ul> | |
*/ | |
public class JTreeEx extends JTree { | |
private static final long serialVersionUID = 1L; | |
private Dimension m_preferredScrollableViewportSize; | |
public JTreeEx() { | |
// focus corrections | |
SwingUtility.installDefaultFocusHandling(this); | |
setFocusCycleRoot(false); | |
// | |
setVerifyInputWhenFocusTarget(true); | |
// | |
setModel(null); | |
getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); | |
setRootVisible(true); | |
// nice: skin needs to improve this to make decent insets | |
setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); | |
// register tree for tooltips | |
ToolTipManager.sharedInstance().registerComponent(this); | |
} | |
//Performance optimization. See: http://hameister.org/JavaSwingJTreePerformance.html | |
//the plaf library calls this method to update the expanded state of a path. | |
//However, the implementation in JTree is very slow, and there is probably no use | |
//for this functionality in Scout. | |
@Override | |
public Enumeration<TreePath> getExpandedDescendants(final TreePath path) { | |
return null; | |
} | |
/** | |
* workaround for bug in swing when using custom tooltips | |
*/ | |
@Override | |
public void setToolTipText(String text) { | |
super.setToolTipText(text); | |
if (text == null) { | |
// re-register | |
ToolTipManager.sharedInstance().registerComponent(this); | |
} | |
} | |
/** | |
* bug in swing: tooltip flickers (show, hide, show, hide,...) when shown at exact mouse position. | |
* <p> | |
* Shift tooltip 4px right, 16px down | |
*/ | |
@Override | |
public Point getToolTipLocation(MouseEvent event) { | |
if (getToolTipText(event) != null) { | |
return new Point(event.getX() + 4, event.getY() + 16); | |
} | |
else { | |
return null; | |
} | |
} | |
// WORKAROUND for background color when disabled | |
// nice: add to look and feel | |
@Override | |
public Color getBackground() { | |
if (!isEnabled()) { | |
Color bg = UIManager.getColor("control"); | |
if (bg != null) { | |
return bg; | |
} | |
} | |
return super.getBackground(); | |
} | |
@Override | |
public void setName(String name) { | |
super.setName(name); | |
firePropertyChange("name", null, name); | |
} | |
@Override | |
public void setTransferHandler(TransferHandler newHandler) { | |
TransferHandler oldHandler = (TransferHandler) getClientProperty("TransferHandler"); | |
putClientProperty("TransferHandler", newHandler); | |
DropTarget dropHandler = getDropTarget(); | |
if ((dropHandler == null) || (dropHandler instanceof UIResource)) { | |
if (newHandler == null) { | |
setDropTarget(null); | |
} | |
else if (!GraphicsEnvironment.isHeadless()) { | |
setDropTarget(new DefaultDropTarget(this)); | |
} | |
} | |
firePropertyChange("transferHandler", oldHandler, newHandler); | |
} | |
public Dimension getPreferredContentSize(int maxRowCount) { | |
Rectangle max = new Rectangle(); | |
for (int r = 0, nr = getRowCount(); r < nr && r < maxRowCount; r++) { | |
max = max.union(getRowBounds(r)); | |
} | |
return new Dimension(max.x + max.width, max.y + max.height); | |
} | |
@Override | |
public TransferHandler getTransferHandler() { | |
return (TransferHandler) getClientProperty("TransferHandler"); | |
} | |
@Override | |
public Dimension getPreferredScrollableViewportSize() { | |
if (m_preferredScrollableViewportSize != null) { | |
return m_preferredScrollableViewportSize; | |
} | |
else { | |
return super.getPreferredScrollableViewportSize(); | |
} | |
} | |
public void setPreferredScrollableViewportSize(Dimension d) { | |
m_preferredScrollableViewportSize = d; | |
} | |
/** | |
* Custom implementation that tries to prevent unnecessary horizontal | |
* scrolling of the tree. It's a copy of the default implementation, | |
* with the only difference that it limits the width of the visible | |
* rect of the target node to <i>max(30 Pixels, 25% of total width)</i>. | |
*/ | |
@Override | |
public void scrollPathToVisible(TreePath treePath) { | |
if (treePath != null) { | |
makeVisible(treePath); | |
Rectangle pathBounds = getPathBounds(treePath); | |
if (pathBounds != null) { | |
// <NoHorizontalScrollPatch> | |
pathBounds.width = Math.max(30, (int) (0.25 * pathBounds.width)); | |
// </NoHorizontalScrollPatch> | |
scrollRectToVisible(pathBounds); | |
if (accessibleContext != null) { | |
((AccessibleJTree) accessibleContext).fireVisibleDataPropertyChange(); | |
} | |
} | |
} | |
} | |
/** | |
* Expands the given path if it is not expanded, or collapses row if it is expanded. If expanding a path and | |
* {@link JTree} scrolls on expand, {@link #ensureRowsAreVisible(int, int)} is invoked to scroll as many of the | |
* children to visible as possible (tries to scroll to last visible descendant of path). | |
*/ | |
public void toggleExpandState(TreePath path) { | |
if (!isExpanded(path)) { | |
expandPath(path); | |
int row = getRowForPath(path); | |
if (row != -1) { | |
int beginRow = row; | |
int endRow = row; | |
if (getScrollsOnExpand()) { | |
// calculate the number of all newly visible children (recursive, not only the direct children) | |
Enumeration<TreePath> descendantToggledPaths = getDescendantToggledPaths(path); | |
int childCount = 0; | |
while (descendantToggledPaths.hasMoreElements()) { | |
TreePath treePath = (TreePath) descendantToggledPaths.nextElement(); | |
if (isExpanded(treePath)) { | |
childCount += getModel().getChildCount(treePath.getLastPathComponent()); | |
} | |
} | |
endRow = beginRow + childCount; | |
} | |
ensureRowsAreVisible(beginRow, endRow); | |
} | |
} | |
else { | |
collapsePath(path); | |
} | |
} | |
/** | |
* Ensure that the rows identified by the given range become visible. | |
* | |
* @param beginRow | |
* index of the first row to be made visible | |
* @param endRow | |
* index of the last row to be made visible | |
*/ | |
private void ensureRowsAreVisible(int beginRow, int endRow) { | |
if (beginRow == endRow) { | |
scrollRowToVisible(beginRow); | |
} | |
else { | |
// calculate the height of newly visible rows and ensure that these area is getting visible | |
Rectangle beginRect = getRowBounds(beginRow); | |
Rectangle visibleRect = getVisibleRect(); | |
Rectangle testRect = beginRect; | |
int beginY = beginRect.y; | |
int maxY = beginY + visibleRect.height; | |
for (int row = beginRow + 1; row <= endRow; row++) { | |
testRect = getRowBounds(row); | |
if ((testRect.y + testRect.height) > maxY) { | |
row = endRow; | |
} | |
} | |
scrollRectToVisible(new Rectangle(visibleRect.x, beginY, 1, testRect.y + testRect.height - beginY)); | |
} | |
} | |
} |