summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/gui/util/TreePopup.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/gui/util/TreePopup.java')
-rw-r--r--src/de/lmu/ifi/dbs/elki/gui/util/TreePopup.java412
1 files changed, 412 insertions, 0 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/gui/util/TreePopup.java b/src/de/lmu/ifi/dbs/elki/gui/util/TreePopup.java
new file mode 100644
index 00000000..60ad0604
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/gui/util/TreePopup.java
@@ -0,0 +1,412 @@
+package de.lmu.ifi.dbs.elki.gui.util;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2014
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ ELKI Development Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GraphicsConfiguration;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+import javax.swing.BoxLayout;
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.border.LineBorder;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreeModel;
+
+/**
+ * Popup menu that contains a JTree.
+ *
+ * @author Erich Schubert
+ */
+public class TreePopup extends JPopupMenu {
+ /**
+ * Serialization version.
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Action string for confirmed operations (enter or click).
+ */
+ public static final String ACTION_SELECTED = "selected";
+
+ /**
+ * Action string for canceled operations (escape button pressed).
+ */
+ public static final String ACTION_CANCELED = "canceled";
+
+ /**
+ * Tree.
+ */
+ protected JTree tree;
+
+ /**
+ * Scroll pane, containing the tree.
+ */
+ protected JScrollPane scroller;
+
+ /**
+ * Tree model.
+ */
+ private TreeModel model;
+
+ /**
+ * Event handler
+ */
+ private Handler handler = new Handler();
+
+ /**
+ * Border of the popup.
+ */
+ private static Border TREE_BORDER = new LineBorder(Color.BLACK, 1);
+
+ /**
+ * Constructor with an empty tree model.
+ *
+ * This needs to also add a root node, and therefore sets
+ * {@code getTree().setRootVisible(false)}.
+ */
+ public TreePopup() {
+ this(new DefaultTreeModel(new DefaultMutableTreeNode()));
+ tree.setRootVisible(false);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param model Tree model
+ */
+ public TreePopup(TreeModel model) {
+ super();
+ this.setName("TreePopup.popup");
+ this.model = model;
+
+ // UI construction of the popup.
+ tree = createTree();
+ scroller = createScroller();
+ configurePopup();
+ }
+
+ /**
+ * Creates the JList used in the popup to display the items in the combo box
+ * model. This method is called when the UI class is created.
+ *
+ * @return a <code>JList</code> used to display the combo box items
+ */
+ protected JTree createTree() {
+ JTree tree = new JTree(model);
+ tree.setName("TreePopup.tree");
+ tree.setFont(getFont());
+ tree.setForeground(getForeground());
+ tree.setBackground(getBackground());
+ tree.setBorder(null);
+ tree.setFocusable(true);
+ tree.addMouseListener(handler);
+ tree.addKeyListener(handler);
+ tree.setCellRenderer(new Renderer());
+ return tree;
+ }
+
+ /**
+ * Configure the popup display.
+ */
+ protected void configurePopup() {
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ setBorderPainted(true);
+ setBorder(TREE_BORDER);
+ setOpaque(false);
+ add(scroller);
+ setDoubleBuffered(true);
+ setFocusable(false);
+ }
+
+ /**
+ * Creates the scroll pane which houses the scrollable tree.
+ */
+ protected JScrollPane createScroller() {
+ JScrollPane sp = new JScrollPane(tree, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ sp.setHorizontalScrollBar(null);
+ sp.setName("TreePopup.scrollPane");
+ sp.setFocusable(false);
+ sp.getVerticalScrollBar().setFocusable(false);
+ sp.setBorder(null);
+ return sp;
+ }
+
+ /**
+ * Access the tree contained.
+ *
+ * @return Tree
+ */
+ public JTree getTree() {
+ return tree;
+ }
+
+ /**
+ * Display the popup, attached to the given component.
+ *
+ * @param parent Parent component
+ */
+ public void show(Component parent) {
+ Dimension parentSize = parent.getSize();
+ Insets insets = getInsets();
+
+ // reduce the width of the scrollpane by the insets so that the popup
+ // is the same width as the combo box.
+ parentSize.setSize(parentSize.width - (insets.right + insets.left), 10 * parentSize.height);
+ Dimension scrollSize = computePopupBounds(parent, 0, getBounds().height, parentSize.width, parentSize.height).getSize();
+
+ scroller.setMaximumSize(scrollSize);
+ scroller.setPreferredSize(scrollSize);
+ scroller.setMinimumSize(scrollSize);
+
+ super.show(parent, 0, parent.getHeight());
+ tree.requestFocusInWindow();
+ }
+
+ protected Rectangle computePopupBounds(Component parent, int px, int py, int pw, int ph) {
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ Rectangle screenBounds;
+
+ // Calculate the desktop dimensions relative to the combo box.
+ GraphicsConfiguration gc = parent.getGraphicsConfiguration();
+ Point p = new Point();
+ SwingUtilities.convertPointFromScreen(p, parent);
+ if(gc != null) {
+ Insets screenInsets = toolkit.getScreenInsets(gc);
+ screenBounds = gc.getBounds();
+ screenBounds.width -= (screenInsets.left + screenInsets.right);
+ screenBounds.height -= (screenInsets.top + screenInsets.bottom);
+ screenBounds.x += (p.x + screenInsets.left);
+ screenBounds.y += (p.y + screenInsets.top);
+ }
+ else {
+ screenBounds = new Rectangle(p, toolkit.getScreenSize());
+ }
+
+ Rectangle rect = new Rectangle(px, py, pw, ph);
+ if(py + ph > screenBounds.y + screenBounds.height && ph < screenBounds.height) {
+ rect.y = -rect.height;
+ }
+ return rect;
+ }
+
+ /**
+ * Register an action listener.
+ *
+ * @param listener Action listener
+ */
+ public void addActionListener(ActionListener listener) {
+ listenerList.add(ActionListener.class, listener);
+ }
+
+ /**
+ * Unregister an action listener.
+ *
+ * @param listener Action listener
+ */
+ public void removeActionListener(ActionListener listener) {
+ listenerList.remove(ActionListener.class, listener);
+ }
+
+ /**
+ * Notify action listeners.
+ *
+ * @param event the <code>ActionEvent</code> object
+ */
+ protected void fireActionPerformed(ActionEvent event) {
+ Object[] listeners = listenerList.getListenerList();
+ for(int i = listeners.length - 2; i >= 0; i -= 2) {
+ if(listeners[i] == ActionListener.class) {
+ ((ActionListener) listeners[i + 1]).actionPerformed(event);
+ }
+ }
+ }
+
+ /**
+ * Tree cell render.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public class Renderer extends JPanel implements TreeCellRenderer {
+ /**
+ * Serial version
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Label to render
+ */
+ JLabel label;
+
+ /**
+ * Colors
+ */
+ private Color selbg, defbg, selfg, deffg;
+
+ /**
+ * Icons
+ */
+ private Icon leafIcon, folderIcon;
+
+ /**
+ * Constructor.
+ */
+ protected Renderer() {
+ selbg = UIManager.getColor("Tree.selectionBackground");
+ defbg = UIManager.getColor("Tree.textBackground");
+ selfg = UIManager.getColor("Tree.selectionForeground");
+ deffg = UIManager.getColor("Tree.textForeground");
+
+ setLayout(new BorderLayout());
+ add(label = new JLabel("This should never be rendered."));
+ }
+
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
+ label.setText((String) ((DefaultMutableTreeNode) value).getUserObject());
+ setForeground(selected ? selfg : deffg);
+ setBackground(selected ? selbg : defbg);
+ label.setIcon(leaf ? leafIcon : folderIcon);
+ setPreferredSize(new Dimension(1000, label.getPreferredSize().height));
+ return this;
+ }
+
+ /**
+ * Set the leaf icon
+ *
+ * @param leafIcon Leaf icon
+ */
+ public void setLeafIcon(Icon leafIcon) {
+ this.leafIcon = leafIcon;
+ }
+
+ /**
+ * Set the folder icon.
+ *
+ * @param folderIcon Folder icon
+ */
+ public void setFolderIcon(Icon folderIcon) {
+ this.folderIcon = folderIcon;
+ }
+ }
+
+ /**
+ * Event handler class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ protected class Handler implements MouseListener, KeyListener, FocusListener {
+ @Override
+ public void keyTyped(KeyEvent e) {
+ if(e.getKeyChar() == '\n') {
+ e.consume();
+ }
+ }
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if(e.getKeyCode() == KeyEvent.VK_ENTER) {
+ fireActionPerformed(new ActionEvent(TreePopup.this, ActionEvent.ACTION_PERFORMED, ACTION_SELECTED, e.getWhen(), e.getModifiers()));
+ e.consume();
+ return;
+ }
+ if(e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ fireActionPerformed(new ActionEvent(TreePopup.this, ActionEvent.ACTION_PERFORMED, ACTION_CANCELED, e.getWhen(), e.getModifiers()));
+ }
+ }
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ if(e.getKeyCode() == KeyEvent.VK_ENTER) {
+ e.consume();
+ }
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if(e.getButton() == MouseEvent.BUTTON1) {
+ fireActionPerformed(new ActionEvent(TreePopup.this, ActionEvent.ACTION_PERFORMED, ACTION_SELECTED, e.getWhen(), e.getModifiers()));
+ }
+ // ignore
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ // ignore
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ // ignore
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ // ignore
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ // ignore
+ }
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ // ignore
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ fireActionPerformed(new ActionEvent(TreePopup.this, ActionEvent.ACTION_PERFORMED, ACTION_CANCELED));
+ }
+ }
+}