/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.plugin.desktoputil.plaf;
import java.awt.*;
import java.awt.Container;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
import javax.swing.tree.*;
/**
* SIPCommTreeUI implementation.
*
* @author Yana Stamcheva
*/
public class SIPCommTreeUI
extends BasicTreeUI
implements HierarchyListener,
TreeSelectionListener
{
private static JTree tree;
private JViewport parentViewport;
private VariableLayoutCache layoutCache;
/**
* Last selected index.
*/
private int lastSelectedIndex;
/**
* Creates the UI for the given component.
* @param c the component for which we're create an UI
* @return this UI implementation
*/
public static ComponentUI createUI(JComponent c)
{
return new SIPCommTreeUI();
}
/**
* Installs this UI to the given component.
* @param c the component to which to install this UI
*/
@Override
public void installUI(JComponent c)
{
if ( c == null )
throw new NullPointerException(
"null component passed to BasicTreeUI.installUI()" );
tree = (JTree)c;
JViewport v = getFirstParentViewport(tree);
if(v != null)
this.parentViewport = v;
else
tree.addHierarchyListener(this);
tree.getSelectionModel().addTreeSelectionListener(this);
super.installUI(c);
}
/**
* Returns the first parent view port found.
* @param c the component parents we search
* @return the first parent view port found.
*/
private JViewport getFirstParentViewport(Container c)
{
if(c == null)
return null;
else
if(c instanceof JViewport)
return (JViewport)c;
else
return getFirstParentViewport(c.getParent());
}
/**
* On uninstalling the ui remove the listeners.
* @param c
*/
@Override
public void uninstallUI(JComponent c)
{
tree.getSelectionModel().clearSelection();
tree.getSelectionModel().removeTreeSelectionListener(this);
tree.removeHierarchyListener(this);
super.uninstallUI(c);
}
/**
* HierarchyListener's method.
* @param e the event.
*/
public void hierarchyChanged(HierarchyEvent e)
{
if (e.getID() == HierarchyEvent.HIERARCHY_CHANGED
&& (e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) != 0
&& e.getChangedParent() instanceof JViewport)
{
parentViewport = (JViewport) e.getChangedParent();
}
}
/**
* The TreeSelectionListener's method.
* @param e the event.
*/
public void valueChanged(TreeSelectionEvent e)
{
// Update cell size.
selectionChanged( e.getOldLeadSelectionPath(),
e.getNewLeadSelectionPath());
}
/**
* Installs the defaults of this UI.
*/
@Override
protected void installDefaults()
{
if(tree.getBackground() == null ||
tree.getBackground() instanceof UIResource) {
tree.setBackground(UIManager.getColor("Tree.background"));
}
if(getHashColor() == null || getHashColor() instanceof UIResource) {
setHashColor(UIManager.getColor("Tree.hash"));
}
if (tree.getFont() == null || tree.getFont() instanceof UIResource)
tree.setFont( UIManager.getFont("Tree.font") );
// JTree's original row height is 16. To correctly display the
// contents on Linux we should have set it to 18, Windows 19 and
// Solaris 20. As these values vary so much it's too hard to
// be backward compatable and try to update the row height, we're
// therefor NOT going to adjust the row height based on font. If the
// developer changes the font, it's there responsibility to update
// the row height.
setExpandedIcon(null);
setCollapsedIcon(null);
setLeftChildIndent(0);
setRightChildIndent(0);
LookAndFeel.installProperty(tree, "rowHeight",
UIManager.get("Tree.rowHeight"));
largeModel = (tree.isLargeModel() && tree.getRowHeight() > 0);
Object scrollsOnExpand = UIManager.get("Tree.scrollsOnExpand");
if (scrollsOnExpand != null) {
LookAndFeel.installProperty(
tree, "scrollsOnExpand", scrollsOnExpand);
}
UIManager.getDefaults().put("Tree.paintLines", false);
UIManager.getDefaults().put("Tree.lineTypeDashed", false);
}
/**
* Creates the object responsible for managing what is expanded, as
* well as the size of nodes.
* @return the created layout cache
*/
@Override
protected AbstractLayoutCache createLayoutCache()
{
layoutCache = new VariableLayoutCache();
return layoutCache;
}
/**
* Do not select the <tt>ShowMoreContact</tt>.
*
* @param path the <tt>TreePath</tt> to select
* @param event the <tt>MouseEvent</tt> that provoked the select
*/
@Override
protected void selectPathForEvent(TreePath path, MouseEvent event)
{
super.selectPathForEvent(path, event);
}
/**
* A custom layout cache that recalculates the width of the cell the match
* the width of the tree (i.e. expands the cell to the right).
*/
private class VariableLayoutCache extends VariableHeightLayoutCache
{
/**
* Returns the preferred width of the receiver.
* @param path the path, which bounds we obtain
* @param placeIn the initial rectangle of the path
* @return the bounds of the path
*/
@Override
public Rectangle getBounds(TreePath path, Rectangle placeIn)
{
Rectangle rect = super.getBounds(path, placeIn);
if (rect != null && parentViewport != null)
{
rect.width = parentViewport.getWidth() - 2;
}
return rect;
}
}
/**
* Ensures the tree size.
*/
private void ensureTreeSize()
{
// Update tree height.
updateSize();
// Finally repaint in order the change to take place.
tree.repaint();
}
/**
* Refreshes row sizes corresponding to the given paths.
*
* @param oldPath the old selection path
* @param newPath the new selection path
*/
public void selectionChanged(TreePath oldPath, TreePath newPath)
{
if (oldPath != null)
layoutCache.invalidatePathBounds(oldPath);
if (newPath != null)
{
layoutCache.invalidatePathBounds(newPath);
lastSelectedIndex = tree.getRowForPath(newPath);
}
// If the selection has disappeared, for example when the selected row
// has been removed, refresh the previously selected row.
else
{
int nextRow = (tree.getRowCount() > lastSelectedIndex)
? lastSelectedIndex : tree.getRowCount() - 1;
layoutCache.invalidatePathBounds(
tree.getPathForRow(nextRow));
}
ensureTreeSize();
}
}