Merge branch 'subtree'
[nikiroo-utils.git] / src / be / nikiroo / utils / ui / BreadCrumbsBar.java
1
2 package be.nikiroo.utils.ui;
3
4 import java.awt.BorderLayout;
5 import java.awt.Dimension;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.awt.event.ComponentAdapter;
9 import java.awt.event.ComponentEvent;
10 import java.util.ArrayList;
11 import java.util.List;
12
13 import javax.swing.AbstractAction;
14 import javax.swing.BoxLayout;
15 import javax.swing.JPanel;
16 import javax.swing.JPopupMenu;
17 import javax.swing.JToggleButton;
18 import javax.swing.SwingWorker;
19 import javax.swing.event.PopupMenuEvent;
20 import javax.swing.event.PopupMenuListener;
21
22 public class BreadCrumbsBar<T> extends ListenerPanel {
23 private class BreadCrumb extends JPanel {
24 private JToggleButton button;
25 private JToggleButton down;
26
27 public BreadCrumb(final DataNode<T> node) {
28 this.setLayout(new BorderLayout());
29
30 if (!node.isRoot()) {
31 button = new JToggleButton(node.toString());
32 button.addActionListener(new ActionListener() {
33 @Override
34 public void actionPerformed(ActionEvent e) {
35 button.setSelected(false);
36 if (!node.isRoot()) {
37 // TODO: allow clicking on root? option?
38 setSelectedNode(node);
39 }
40 }
41 });
42
43 this.add(button, BorderLayout.CENTER);
44 }
45
46 if (!node.getChildren().isEmpty()) {
47 // TODO allow an image or ">", viewer
48 down = new JToggleButton(">");
49 final JPopupMenu popup = new JPopupMenu();
50
51 for (final DataNode<T> child : node.getChildren()) {
52 popup.add(new AbstractAction(child.toString()) {
53 private static final long serialVersionUID = 1L;
54
55 @Override
56 public void actionPerformed(ActionEvent e) {
57 setSelectedNode(child);
58 }
59 });
60 }
61
62 down.addActionListener(new ActionListener() {
63 @Override
64 public void actionPerformed(ActionEvent ev) {
65 if (down.isSelected()) {
66 popup.show(down, 0, down.getBounds().height);
67 } else {
68 popup.setVisible(false);
69 }
70 }
71 });
72
73 popup.addPopupMenuListener(new PopupMenuListener() {
74 @Override
75 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
76 }
77
78 @Override
79 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
80 down.setSelected(false);
81 }
82
83 @Override
84 public void popupMenuCanceled(PopupMenuEvent e) {
85 }
86 });
87
88 this.add(down, BorderLayout.EAST);
89 }
90 }
91 }
92
93 static public final String CHANGE_ACTION = "change";
94
95 private boolean vertical;
96 private DataNode<T> node;
97 private List<BreadCrumb> crumbs = new ArrayList<BreadCrumb>();
98
99 public BreadCrumbsBar(final DataTree<T> tree) {
100 vertical = true; // to force an update
101 setVertical(false);
102
103 addComponentListener(new ComponentAdapter() {
104 @Override
105 public void componentResized(ComponentEvent e) {
106 super.componentResized(e);
107 synchronized (crumbs) {
108 for (BreadCrumb crumb : crumbs) {
109 setCrumbSize(crumb);
110 }
111 }
112 }
113 });
114
115 new SwingWorker<DataNode<T>, Void>() {
116 @Override
117 protected DataNode<T> doInBackground() throws Exception {
118 tree.loadData();
119 return tree.getRoot();
120 }
121
122 @Override
123 protected void done() {
124 try {
125 node = get();
126 addCrumb(node);
127
128 // TODO: option?
129 if (node.size() > 0) {
130 setSelectedNode(node.getChildren().get(0));
131 } else {
132 revalidate();
133 repaint();
134 }
135 } catch (Exception e) {
136 e.printStackTrace();
137 }
138 }
139 }.execute();
140 }
141
142 public void setVertical(boolean vertical) {
143 if (vertical != this.vertical) {
144 synchronized (crumbs) {
145 this.vertical = vertical;
146
147 for (BreadCrumb crumb : crumbs) {
148 this.remove(crumb);
149 }
150
151 if (vertical) {
152 this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
153 } else {
154 this.setLayout(new WrapLayout(WrapLayout.LEADING));
155 }
156
157 for (BreadCrumb crumb : crumbs) {
158 this.add(crumb);
159 setCrumbSize(crumb);
160 }
161 }
162
163 this.revalidate();
164 this.repaint();
165 }
166 }
167
168 public DataNode<T> getSelectedNode() {
169 return node;
170 }
171
172 public void setSelectedNode(DataNode<T> node) {
173 if (this.node == node) {
174 return;
175 }
176
177 synchronized (crumbs) {
178 // clear until common ancestor (can clear all!)
179 while (this.node != null && !this.node.isParentOf(node)) {
180 this.node = this.node.getParent();
181 this.remove(crumbs.remove(crumbs.size() - 1));
182 }
183
184 // switch root if needed and possible
185 if (this.node == null && node != null) {
186 this.node = node.getRoot();
187 addCrumb(this.node);
188 }
189
190 // re-create until node
191 while (node != null && this.node != node) {
192 DataNode<T> ancestorOrNode = node;
193 for (DataNode<T> child : this.node.getChildren()) {
194 if (child.isParentOf(node))
195 ancestorOrNode = child;
196 }
197
198 this.node = ancestorOrNode;
199 addCrumb(this.node);
200 }
201 }
202
203 this.revalidate();
204 this.repaint();
205
206 fireActionPerformed(CHANGE_ACTION);
207 }
208
209 private void addCrumb(DataNode<T> node) {
210 BreadCrumb crumb = new BreadCrumb(node);
211 this.crumbs.add(crumb);
212 setCrumbSize(crumb);
213 this.add(crumb);
214 }
215
216 private void setCrumbSize(BreadCrumb crumb) {
217 if (vertical) {
218 crumb.setMaximumSize(new Dimension(this.getWidth(),
219 crumb.getMinimumSize().height));
220 } else {
221 crumb.setMaximumSize(null);
222 }
223 }
224 }