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