blob: a131695030398cd444d7c62c2c006d637315cacf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.editor.breadcrumbs;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.BeanInfo;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.LineBorder;
import org.netbeans.api.actions.Openable;
import org.netbeans.editor.JumpList;
import org.netbeans.modules.editor.breadcrumbs.spi.BreadcrumbsController;
import org.openide.awt.HtmlRenderer;
import org.openide.awt.HtmlRenderer.Renderer;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.ListView;
import org.openide.explorer.view.NodeRenderer;
import org.openide.nodes.Node;
import org.openide.util.ImageUtilities;
import org.openide.util.WeakListeners;
/**
*
* @author lahvac
*/
public class BreadCrumbComponent<T extends JLabel&Renderer> extends JComponent implements PropertyChangeListener {
private final Icon SEPARATOR =
ImageUtilities.image2Icon(ImageUtilities.loadImage("org/netbeans/modules/editor/breadcrumbs/resources/separator.png"));
public BreadCrumbComponent() {
setPreferredSize(new Dimension(0, COMPONENT_HEIGHT));
addMouseListener(new MouseAdapter() {
@Override public void mouseClicked(MouseEvent e) {
expand(e);
}
});
}
private static final int USABLE_HEIGHT = 19;
private static final int LEFT_SEPARATOR_INSET = 2;
private static final int RIGHT_SEPARATOR_INSET = 10;
private static final int ICON_TEXT_SEPARATOR = 5;
private static final int START_INSET = 8;
private static final int MAX_ROWS_IN_POP_UP = 20;
public static final int COMPONENT_HEIGHT = USABLE_HEIGHT;
private ExplorerManager seenManager;
private PropertyChangeListener weakPCL;
private ExplorerManager findManager() {
ExplorerManager manager = ExplorerManager.find(this);
if (seenManager != manager) {
if (seenManager != null && weakPCL != null) {
seenManager.removePropertyChangeListener(weakPCL);
weakPCL = null;
}
if (manager != null) {
weakPCL = WeakListeners.propertyChange(this, manager);
manager.addPropertyChangeListener(weakPCL);
}
seenManager = manager;
}
assert manager != null;
return manager;
}
@Override
protected void paintComponent(Graphics g) {
if (nodes == null) measurePrepaint();
assert nodes != null;
if( "Aqua".equals(UIManager.getLookAndFeel().getID()) ) //NOI18N
setBackground(UIManager.getColor("NbExplorerView.background")); //NOI18N
int height = getHeight();
if (nodes.length == 0) {
SEPARATOR.paintIcon(this, g, START_INSET, (height - SEPARATOR.getIconHeight()) / 2);
return ;
}
int x = START_INSET;
for (int i = 0; i < nodes.length; i++) {
configureForNode(nodes[i]);
Dimension preferred = renderer.getPreferredSize();
int labelY = (height - preferred.height) / 2;
g.translate(x, labelY);
renderer.setSize(preferred);
renderer.paint(g);
g.translate(-x, -labelY);
x += sizes[i];
SEPARATOR.paintIcon(this, g, x + LEFT_SEPARATOR_INSET, (height - SEPARATOR.getIconHeight()) / 2);
x += LEFT_SEPARATOR_INSET + SEPARATOR.getIconWidth() + RIGHT_SEPARATOR_INSET;
}
}
private final T renderer = (T) HtmlRenderer.createLabel();
private Node[] nodes;
private double[] sizes;
private double height;
private void measurePrepaint() {
List<Node> path = computeNodePath();
int i = 0;
nodes = path.toArray(new Node[path.size()]);
sizes = new double[path.size()];
int xTotal = 0;
height = /*XXX*/0;
for (Node n : nodes) {
configureForNode(n);
Dimension preferedSize = renderer.getPreferredSize();
xTotal += sizes[i] = preferedSize.width;
height = Math.max(height, preferedSize.height);
i++;
}
setPreferredSize(new Dimension((xTotal + (nodes.length - 1) * (LEFT_SEPARATOR_INSET + SEPARATOR.getIconWidth() + RIGHT_SEPARATOR_INSET) + START_INSET), USABLE_HEIGHT/*(int) (height + 2 * INSET_HEIGHT)*/));
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
SwingUtilities.invokeLater(new Runnable() {
@Override public void run() {
measurePrepaint();
repaint();
}
});
}
private void expand(MouseEvent e) {
int clickX = e.getPoint().x;
int elemX = START_INSET;
for (int i = 0; i < sizes.length; i++) {
int startX = elemX;
elemX += sizes[i];
elemX += LEFT_SEPARATOR_INSET;
if (clickX <= elemX) {
//found:
List<Node> path = computeNodePath();
Node selected = path.get(i);
if (e.getButton() == MouseEvent.BUTTON1) {
open(selected);
} else {
expand(startX, selected);
}
return ;
}
startX = elemX;
elemX += SEPARATOR.getIconWidth();
if (clickX <= elemX) {
//found:
List<Node> path = computeNodePath();
expand(startX, path.get(i));
return ;
}
elemX += RIGHT_SEPARATOR_INSET;
}
}
private List<Node> computeNodePath() {
ExplorerManager manager = findManager();
List<Node> path = new ArrayList<Node>();
Node sel = manager.getExploredContext();
// see #223480; root context need not be the root of the node structure.
Node stopAt = manager.getRootContext().getParentNode();
while (sel != null && sel != stopAt) {
path.add(sel);
sel = sel.getParentNode();
}
path.remove(path.size() - 1); //XXX
Collections.reverse(path);
return path;
}
private void open(Node node) {
Openable openable = node.getLookup().lookup(Openable.class);
if (openable != null) {
JumpList.checkAddEntry();
openable.open();
}
}
private void expand(int startX, final Node what) {
if (what.getChildren().getNodesCount() == 0) return ;
final ExplorerManager expandManager = new ExplorerManager();
class Expanded extends JPanel implements ExplorerManager.Provider {
public Expanded(LayoutManager layout) {
super(layout);
}
@Override public ExplorerManager getExplorerManager() {
return expandManager;
}
}
final JPanel expanded = new Expanded(new BorderLayout());
expanded.setBorder(new LineBorder(Color.BLACK, 1));
final ListView listView = new ListView() {
{
int nodesCount = what.getChildren().getNodesCount();
if (nodesCount >= MAX_ROWS_IN_POP_UP) {
list.setVisibleRowCount(MAX_ROWS_IN_POP_UP);
} else {
list.setVisibleRowCount(nodesCount);
NodeRenderer nr = new NodeRenderer();
int i = 0;
int width = getPreferredSize().width;
for (Node n : what.getChildren().getNodes()) {
if (nr.getListCellRendererComponent(list, n, i, false, false).getPreferredSize().width > width) {
Dimension pref = getPreferredSize();
pref.height += getHorizontalScrollBar().getPreferredSize().height;
setPreferredSize(pref);
break;
}
}
}
}
};
listView.setPopupAllowed(false);
expanded.add(listView, BorderLayout.CENTER);
expandManager.setRootContext(what);
Point place = new Point(startX, 0);
SwingUtilities.convertPointToScreen(place, this);
expanded.validate();
final Popup popup = PopupFactory.getSharedInstance().getPopup(this, expanded, place.x, place.y - expanded.getPreferredSize().height);
final AWTEventListener multicastListener = new AWTEventListener() {
@Override public void eventDispatched(AWTEvent event) {
if (event instanceof MouseEvent && ((MouseEvent) event).getClickCount() > 0) {
Object source = event.getSource();
while (source instanceof Component) {
if (source == expanded) return ; //accept
source = ((Component) source).getParent();
}
popup.hide();
Toolkit.getDefaultToolkit().removeAWTEventListener(this);
}
}
};
Toolkit.getDefaultToolkit().addAWTEventListener(multicastListener, AWTEvent.MOUSE_EVENT_MASK);
expandManager.addPropertyChangeListener(new PropertyChangeListener() {
@Override public void propertyChange(PropertyChangeEvent evt) {
if (ExplorerManager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) {
Node[] selected = expandManager.getSelectedNodes();
if (selected.length == 1) {
open(selected[0]);
popup.hide();
Toolkit.getDefaultToolkit().removeAWTEventListener(multicastListener);
}
}
}
});
popup.show();
}
private void configureForNode(Node node) {
renderer.reset();
Image nodeIcon = node.getIcon(BeanInfo.ICON_COLOR_16x16);
Icon icon = nodeIcon != null && nodeIcon != BreadcrumbsController.NO_ICON ? ImageUtilities.image2Icon(nodeIcon) : null;
int width = icon != null ? icon.getIconWidth() : 0;
if (width > 0) {
renderer.setIcon(icon);
renderer.setIconTextGap(ICON_TEXT_SEPARATOR);
} else {
renderer.setIcon(null);
renderer.setIconTextGap(0);
}
String html = node.getHtmlDisplayName();
if (html != null) {
renderer.setHtml(true);
renderer.setText(html);
} else {
renderer.setHtml(false);
renderer.setText(node.getDisplayName());
}
renderer.setFont(getFont());
}
}