Bundle: fix memory leak at init/reset
[fanfix.git] / ui / BreadCrumbsBar.java
CommitLineData
a917f100
NR
1
2package be.nikiroo.utils.ui;
3
4import java.awt.BorderLayout;
5import java.awt.Dimension;
a917f100
NR
6import java.awt.event.ActionEvent;
7import java.awt.event.ActionListener;
8import java.awt.event.ComponentAdapter;
9import java.awt.event.ComponentEvent;
10import java.util.ArrayList;
11import java.util.List;
12
13import javax.swing.AbstractAction;
14import javax.swing.BoxLayout;
a917f100
NR
15import javax.swing.JPanel;
16import javax.swing.JPopupMenu;
17import javax.swing.JToggleButton;
18import javax.swing.SwingWorker;
19import javax.swing.event.PopupMenuEvent;
20import javax.swing.event.PopupMenuListener;
21
22public class BreadCrumbsBar<T> extends ListenerPanel {
23 private class BreadCrumb extends JPanel {
416741f5 24 private JToggleButton button;
a917f100
NR
25 private JToggleButton down;
26
27 public BreadCrumb(final DataNode<T> node) {
28 this.setLayout(new BorderLayout());
29
a917f100 30 if (!node.isRoot()) {
416741f5 31 button = new JToggleButton(node.toString());
a917f100
NR
32 button.addActionListener(new ActionListener() {
33 @Override
34 public void actionPerformed(ActionEvent e) {
416741f5
NR
35 button.setSelected(false);
36 if (!node.isRoot()) {
37 // TODO: allow clicking on root? option?
38 setSelectedNode(node);
39 }
a917f100
NR
40 }
41 });
7ce18848 42
416741f5 43 this.add(button, BorderLayout.CENTER);
a917f100
NR
44 }
45
7ce18848
NR
46 if ((node.isRoot() && node.getChildren().isEmpty())
47 || !node.getChildren().isEmpty()) {
b5d21237 48 // TODO allow an image or ">", viewer
a917f100
NR
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()) {
a917f100
NR
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() {
a917f100
NR
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
7ce18848
NR
116 setSelectedNode(new DataNode<T>(null, null));
117
a917f100
NR
118 new SwingWorker<DataNode<T>, Void>() {
119 @Override
120 protected DataNode<T> doInBackground() throws Exception {
121 tree.loadData();
122 return tree.getRoot();
123 }
124
125 @Override
126 protected void done() {
127 try {
7ce18848
NR
128 DataNode<T> node = get();
129
130 setSelectedNode(null);
131 BreadCrumbsBar.this.node = node;
a917f100
NR
132 addCrumb(node);
133
134 // TODO: option?
135 if (node.size() > 0) {
136 setSelectedNode(node.getChildren().get(0));
137 } else {
138 revalidate();
139 repaint();
140 }
141 } catch (Exception e) {
142 e.printStackTrace();
143 }
144 }
145 }.execute();
146 }
147
148 public void setVertical(boolean vertical) {
149 if (vertical != this.vertical) {
150 synchronized (crumbs) {
151 this.vertical = vertical;
152
153 for (BreadCrumb crumb : crumbs) {
154 this.remove(crumb);
155 }
156
157 if (vertical) {
158 this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
159 } else {
e7437397 160 this.setLayout(new WrapLayout(WrapLayout.LEADING));
a917f100
NR
161 }
162
163 for (BreadCrumb crumb : crumbs) {
164 this.add(crumb);
165 setCrumbSize(crumb);
166 }
167 }
168
169 this.revalidate();
170 this.repaint();
171 }
172 }
173
174 public DataNode<T> getSelectedNode() {
175 return node;
176 }
177
178 public void setSelectedNode(DataNode<T> node) {
179 if (this.node == node) {
180 return;
181 }
182
183 synchronized (crumbs) {
184 // clear until common ancestor (can clear all!)
185 while (this.node != null && !this.node.isParentOf(node)) {
186 this.node = this.node.getParent();
187 this.remove(crumbs.remove(crumbs.size() - 1));
188 }
189
190 // switch root if needed and possible
191 if (this.node == null && node != null) {
192 this.node = node.getRoot();
193 addCrumb(this.node);
194 }
195
196 // re-create until node
197 while (node != null && this.node != node) {
198 DataNode<T> ancestorOrNode = node;
199 for (DataNode<T> child : this.node.getChildren()) {
200 if (child.isParentOf(node))
201 ancestorOrNode = child;
202 }
203
204 this.node = ancestorOrNode;
205 addCrumb(this.node);
206 }
207 }
208
209 this.revalidate();
210 this.repaint();
211
212 fireActionPerformed(CHANGE_ACTION);
213 }
214
215 private void addCrumb(DataNode<T> node) {
216 BreadCrumb crumb = new BreadCrumb(node);
217 this.crumbs.add(crumb);
218 setCrumbSize(crumb);
219 this.add(crumb);
220 }
221
222 private void setCrumbSize(BreadCrumb crumb) {
223 if (vertical) {
224 crumb.setMaximumSize(new Dimension(this.getWidth(),
225 crumb.getMinimumSize().height));
226 } else {
227 crumb.setMaximumSize(null);
228 }
229 }
230}