add UI utils from fanfix-swing to nikiroo-utils
[nikiroo-utils.git] / 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.JButton;
17 import javax.swing.JPanel;
18 import javax.swing.JPopupMenu;
19 import javax.swing.JToggleButton;
20 import javax.swing.SwingWorker;
21 import javax.swing.event.PopupMenuEvent;
22 import javax.swing.event.PopupMenuListener;
23
24 public class BreadCrumbsBar<T> extends ListenerPanel {
25 private class BreadCrumb extends JPanel {
26 private JButton button;
27 private JToggleButton down;
28
29 public BreadCrumb(final DataNode<T> node) {
30 this.setLayout(new BorderLayout());
31
32 button = new JButton(node.toString());
33 if (!node.isRoot()) {
34 // TODO: allow clicking on root? option?
35 button.addActionListener(new ActionListener() {
36 @Override
37 public void actionPerformed(ActionEvent e) {
38 setSelectedNode(node);
39 }
40 });
41 }
42
43 this.add(button, BorderLayout.CENTER);
44
45 if (!node.getChildren().isEmpty()) {
46 // TODO (see things with icons included in viewer)
47 down = new JToggleButton(">");
48 final JPopupMenu popup = new JPopupMenu();
49
50 for (final DataNode<T> child : node.getChildren()) {
51 popup.add(new AbstractAction(child.toString()) {
52
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
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 FlowLayout(FlowLayout.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 }