Merge commit 'ee020e75a5de0c1e54557db79f77edc50e05ea04'
[fanfix.git] / src / be / nikiroo / utils / ui / BreadCrumbsBar.java
diff --git a/src/be/nikiroo/utils/ui/BreadCrumbsBar.java b/src/be/nikiroo/utils/ui/BreadCrumbsBar.java
new file mode 100644 (file)
index 0000000..8b993e6
--- /dev/null
@@ -0,0 +1,225 @@
+
+package be.nikiroo.utils.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JToggleButton;
+import javax.swing.SwingWorker;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+
+public class BreadCrumbsBar<T> extends ListenerPanel {
+       private class BreadCrumb extends JPanel {
+               private JToggleButton button;
+               private JToggleButton down;
+
+               public BreadCrumb(final DataNode<T> node) {
+                       this.setLayout(new BorderLayout());
+
+                       if (!node.isRoot()) {
+                               button = new JToggleButton(node.toString());
+                               button.addActionListener(new ActionListener() {
+                                       @Override
+                                       public void actionPerformed(ActionEvent e) {
+                                               button.setSelected(false);
+                                               if (!node.isRoot()) {
+                                                       // TODO: allow clicking on root? option?
+                                                       setSelectedNode(node);
+                                               }
+                                       }
+                               });
+                               
+                               this.add(button, BorderLayout.CENTER);
+                       }
+
+                       if (!node.getChildren().isEmpty()) {
+                               // TODO (see things with icons included in viewer)
+                               down = new JToggleButton(">");
+                               final JPopupMenu popup = new JPopupMenu();
+
+                               for (final DataNode<T> child : node.getChildren()) {
+                                       popup.add(new AbstractAction(child.toString()) {
+                                               private static final long serialVersionUID = 1L;
+
+                                               @Override
+                                               public void actionPerformed(ActionEvent e) {
+                                                       setSelectedNode(child);
+                                               }
+                                       });
+                               }
+
+                               down.addActionListener(new ActionListener() {
+                                       @Override
+                                       public void actionPerformed(ActionEvent ev) {
+                                               if (down.isSelected()) {
+                                                       popup.show(down, 0, down.getBounds().height);
+                                               } else {
+                                                       popup.setVisible(false);
+                                               }
+                                       }
+                               });
+
+                               popup.addPopupMenuListener(new PopupMenuListener() {
+                                       @Override
+                                       public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+                                       }
+
+                                       @Override
+                                       public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+                                               down.setSelected(false);
+                                       }
+
+                                       @Override
+                                       public void popupMenuCanceled(PopupMenuEvent e) {
+                                       }
+                               });
+
+                               this.add(down, BorderLayout.EAST);
+                       }
+               }
+       }
+
+       static public final String CHANGE_ACTION = "change";
+
+       private boolean vertical;
+       private DataNode<T> node;
+       private List<BreadCrumb> crumbs = new ArrayList<BreadCrumb>();
+
+       public BreadCrumbsBar(final DataTree<T> tree) {
+               vertical = true; // to force an update
+               setVertical(false);
+
+               addComponentListener(new ComponentAdapter() {
+                       @Override
+                       public void componentResized(ComponentEvent e) {
+                               super.componentResized(e);
+                               synchronized (crumbs) {
+                                       for (BreadCrumb crumb : crumbs) {
+                                               setCrumbSize(crumb);
+                                       }
+                               }
+                       }
+               });
+
+               new SwingWorker<DataNode<T>, Void>() {
+                       @Override
+                       protected DataNode<T> doInBackground() throws Exception {
+                               tree.loadData();
+                               return tree.getRoot();
+                       }
+
+                       @Override
+                       protected void done() {
+                               try {
+                                       node = get();
+                                       addCrumb(node);
+
+                                       // TODO: option?
+                                       if (node.size() > 0) {
+                                               setSelectedNode(node.getChildren().get(0));
+                                       } else {
+                                               revalidate();
+                                               repaint();
+                                       }
+                               } catch (Exception e) {
+                                       e.printStackTrace();
+                               }
+                       }
+               }.execute();
+       }
+
+       public void setVertical(boolean vertical) {
+               if (vertical != this.vertical) {
+                       synchronized (crumbs) {
+                               this.vertical = vertical;
+
+                               for (BreadCrumb crumb : crumbs) {
+                                       this.remove(crumb);
+                               }
+
+                               if (vertical) {
+                                       this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+                               } else {
+                                       this.setLayout(new WrapLayout(WrapLayout.LEADING));
+                               }
+
+                               for (BreadCrumb crumb : crumbs) {
+                                       this.add(crumb);
+                                       setCrumbSize(crumb);
+                               }
+                       }
+
+                       this.revalidate();
+                       this.repaint();
+               }
+       }
+
+       public DataNode<T> getSelectedNode() {
+               return node;
+       }
+
+       public void setSelectedNode(DataNode<T> node) {
+               if (this.node == node) {
+                       return;
+               }
+
+               synchronized (crumbs) {
+                       // clear until common ancestor (can clear all!)
+                       while (this.node != null && !this.node.isParentOf(node)) {
+                               this.node = this.node.getParent();
+                               this.remove(crumbs.remove(crumbs.size() - 1));
+                       }
+
+                       // switch root if needed and possible
+                       if (this.node == null && node != null) {
+                               this.node = node.getRoot();
+                               addCrumb(this.node);
+                       }
+
+                       // re-create until node
+                       while (node != null && this.node != node) {
+                               DataNode<T> ancestorOrNode = node;
+                               for (DataNode<T> child : this.node.getChildren()) {
+                                       if (child.isParentOf(node))
+                                               ancestorOrNode = child;
+                               }
+
+                               this.node = ancestorOrNode;
+                               addCrumb(this.node);
+                       }
+               }
+
+               this.revalidate();
+               this.repaint();
+
+               fireActionPerformed(CHANGE_ACTION);
+       }
+
+       private void addCrumb(DataNode<T> node) {
+               BreadCrumb crumb = new BreadCrumb(node);
+               this.crumbs.add(crumb);
+               setCrumbSize(crumb);
+               this.add(crumb);
+       }
+
+       private void setCrumbSize(BreadCrumb crumb) {
+               if (vertical) {
+                       crumb.setMaximumSize(new Dimension(this.getWidth(),
+                                       crumb.getMinimumSize().height));
+               } else {
+                       crumb.setMaximumSize(null);
+               }
+       }
+}