Bundle: fix memory leak at init/reset
[fanfix.git] / ui / NavBar.java
1 package be.nikiroo.utils.ui;
2
3 import java.awt.Dimension;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.ActionListener;
6
7 import javax.swing.BorderFactory;
8 import javax.swing.BoxLayout;
9 import javax.swing.Icon;
10 import javax.swing.JButton;
11 import javax.swing.JLabel;
12 import javax.swing.JTextField;
13
14 /**
15 * A Swing-based navigation bar, that displays first/previous/next/last page
16 * buttons.
17 *
18 * @author niki
19 */
20 public class NavBar extends ListenerPanel {
21 private static final long serialVersionUID = 1L;
22
23 /** The event that is fired on page change. */
24 public static final String PAGE_CHANGED = "page changed";
25
26 private JTextField page;
27 private JLabel pageLabel;
28 private JLabel maxPage;
29 private JLabel label;
30
31 private int index = 0;
32 private int min = 0;
33 private int max = 0;
34 private String extraLabel = null;
35
36 private boolean vertical;
37
38 private JButton first;
39 private JButton previous;
40 private JButton next;
41 private JButton last;
42
43 /**
44 * Create a new navigation bar.
45 * <p>
46 * The minimum must be lower or equal to the maximum, but a max of "-1"
47 * means "infinite".
48 * <p>
49 * A {@link NavBar#PAGE_CHANGED} event will be fired on startup.
50 *
51 * @param min
52 * the minimum page number (cannot be negative)
53 * @param max
54 * the maximum page number (cannot be lower than min, except if
55 * -1 (infinite))
56 *
57 * @throws IndexOutOfBoundsException
58 * if min &gt; max and max is not "-1"
59 */
60 public NavBar(int min, int max) {
61 if (min > max && max != -1) {
62 throw new IndexOutOfBoundsException(
63 String.format("min (%d) > max (%d)", min, max));
64 }
65
66 // Page navigation
67 first = new JButton();
68 first.addActionListener(new ActionListener() {
69 @Override
70 public void actionPerformed(ActionEvent e) {
71 first();
72 }
73 });
74
75 previous = new JButton();
76 previous.addActionListener(new ActionListener() {
77 @Override
78 public void actionPerformed(ActionEvent e) {
79 previous();
80 }
81 });
82
83 final int defaultHeight = new JButton("dummy")
84 .getPreferredSize().height;
85 final int width4 = new JButton("1234").getPreferredSize().width;
86 page = new JTextField(Integer.toString(min));
87 page.setPreferredSize(new Dimension(width4, defaultHeight));
88 page.addActionListener(new ActionListener() {
89 @Override
90 public void actionPerformed(ActionEvent e) {
91 try {
92 int pageNb = Integer.parseInt(page.getText());
93 if (pageNb < NavBar.this.min || pageNb > NavBar.this.max) {
94 throw new NumberFormatException("invalid");
95 }
96
97 if (setIndex(pageNb))
98 fireActionPerformed(PAGE_CHANGED);
99 } catch (NumberFormatException nfe) {
100 page.setText(Integer.toString(index));
101 }
102 }
103 });
104
105 pageLabel = new JLabel(Integer.toString(min));
106 pageLabel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
107
108 maxPage = new JLabel("of " + max);
109 maxPage.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
110
111 next = new JButton();
112 next.addActionListener(new ActionListener() {
113 @Override
114 public void actionPerformed(ActionEvent e) {
115 next();
116 }
117 });
118
119 last = new JButton();
120 last.addActionListener(new ActionListener() {
121 @Override
122 public void actionPerformed(ActionEvent e) {
123 last();
124 }
125 });
126
127 label = new JLabel("");
128
129 // Set the << < > >> "icons"
130 setIcons(null, null, null, null);
131
132 this.min = min;
133 this.max = max;
134 this.index = min;
135
136 updateEnabled();
137 updateLabel();
138 setOrientation(vertical);
139
140 fireActionPerformed(PAGE_CHANGED);
141 }
142
143 /**
144 * The current index, must be between {@link NavBar#min} and
145 * {@link NavBar#max}, both inclusive.
146 *
147 * @return the index
148 */
149 public int getIndex() {
150 return index;
151 }
152
153 /**
154 * The current index, should be between {@link NavBar#min} and
155 * {@link NavBar#max}, both inclusive.
156 *
157 * @param index
158 * the new index
159 *
160 * @return TRUE if the index changed, FALSE if not (either it was already at
161 * that value, or it is outside of the bounds set by
162 * {@link NavBar#min} and {@link NavBar#max})
163 */
164 public synchronized boolean setIndex(int index) {
165 if (index != this.index) {
166 if (index < min || (index > max && max != -1)) {
167 return false;
168 }
169
170 this.index = index;
171 updateLabel();
172 updateEnabled();
173
174 return true;
175 }
176
177 return false;
178 }
179
180 /**
181 * The minimun page number. Cannot be negative.
182 *
183 * @return the min
184 */
185 public int getMin() {
186 return min;
187 }
188
189 /**
190 * The minimum page number. Cannot be negative.
191 * <p>
192 * May update the index if needed (if the index is &lt; the new min).
193 * <p>
194 * Will also (always) update the label and enable/disable the required
195 * buttons.
196 *
197 * @param min
198 * the new min
199 */
200 public synchronized void setMin(int min) {
201 this.min = min;
202 if (index < min) {
203 index = min;
204 }
205
206 updateEnabled();
207 updateLabel();
208 }
209
210 /**
211 * The maximum page number. Cannot be lower than min, except if -1
212 * (infinite).
213 *
214 * @return the max
215 */
216 public int getMax() {
217 return max;
218 }
219
220 /**
221 * The maximum page number. Cannot be lower than min, except if -1
222 * (infinite).
223 * <p>
224 * May update the index if needed (if the index is &gt; the new max).
225 * <p>
226 * Will also (always) update the label and enable/disable the required
227 * buttons.
228 *
229 * @param max
230 * the new max
231 */
232 public synchronized void setMax(int max) {
233 this.max = max;
234 if (index > max && max != -1) {
235 index = max;
236 }
237
238 maxPage.setText("of " + max);
239 updateEnabled();
240 updateLabel();
241 }
242
243 /**
244 * The current extra label to display.
245 *
246 * @return the current label
247 */
248 public String getExtraLabel() {
249 return extraLabel;
250 }
251
252 /**
253 * The current extra label to display.
254 *
255 * @param currentLabel
256 * the new current label
257 */
258 public void setExtraLabel(String currentLabel) {
259 this.extraLabel = currentLabel;
260 updateLabel();
261 }
262
263 /**
264 * Change the page to the next one.
265 *
266 * @return TRUE if it changed
267 */
268 public synchronized boolean next() {
269 if (setIndex(index + 1)) {
270 fireActionPerformed(PAGE_CHANGED);
271 return true;
272 }
273
274 return false;
275 }
276
277 /**
278 * Change the page to the previous one.
279 *
280 * @return TRUE if it changed
281 */
282 public synchronized boolean previous() {
283 if (setIndex(index - 1)) {
284 fireActionPerformed(PAGE_CHANGED);
285 return true;
286 }
287
288 return false;
289 }
290
291 /**
292 * Change the page to the first one.
293 *
294 * @return TRUE if it changed
295 */
296 public synchronized boolean first() {
297 if (setIndex(min)) {
298 fireActionPerformed(PAGE_CHANGED);
299 return true;
300 }
301
302 return false;
303 }
304
305 /**
306 * Change the page to the last one.
307 *
308 * @return TRUE if it changed
309 */
310 public synchronized boolean last() {
311 if (setIndex(max)) {
312 fireActionPerformed(PAGE_CHANGED);
313 return true;
314 }
315
316 return false;
317 }
318
319 /**
320 * Set icons for the buttons instead of square brackets.
321 * <p>
322 * Any NULL value will make the button use square brackets again.
323 *
324 * @param first
325 * the icon of the button "go to first page"
326 * @param previous
327 * the icon of the button "go to previous page"
328 * @param next
329 * the icon of the button "go to next page"
330 * @param last
331 * the icon of the button "go to last page"
332 */
333 public void setIcons(Icon first, Icon previous, Icon next, Icon last) {
334 this.first.setIcon(first);
335 this.first.setText(first == null ? "<<" : "");
336 this.previous.setIcon(previous);
337 this.previous.setText(previous == null ? "<" : "");
338 this.next.setIcon(next);
339 this.next.setText(next == null ? ">" : "");
340 this.last.setIcon(last);
341 this.last.setText(last == null ? ">>" : "");
342 }
343
344 /**
345 * The general orientation of the component.
346 *
347 * @return TRUE for vertical orientation, FALSE for horisontal orientation
348 */
349 public boolean getOrientation() {
350 return vertical;
351 }
352
353 /**
354 * Update the general orientation of the component.
355 *
356 * @param vertical
357 * TRUE for vertical orientation, FALSE for horisontal
358 * orientation
359 *
360 * @return TRUE if it changed something
361 */
362 public boolean setOrientation(boolean vertical) {
363 if (getWidth() == 0 || this.vertical != vertical) {
364 this.vertical = vertical;
365
366 BoxLayout layout = new BoxLayout(this,
367 vertical ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS);
368 this.removeAll();
369 setLayout(layout);
370
371 this.add(first);
372 this.add(previous);
373 if (vertical) {
374 this.add(pageLabel);
375 } else {
376 this.add(page);
377 }
378 this.add(maxPage);
379 this.add(next);
380 this.add(last);
381
382 if (!vertical) {
383 this.add(label);
384 }
385
386 this.revalidate();
387 this.repaint();
388
389 return true;
390 }
391
392 return false;
393 }
394
395 /**
396 * Update the label displayed in the UI.
397 */
398 private void updateLabel() {
399 label.setText(getExtraLabel());
400 pageLabel.setText(Integer.toString(index));
401 page.setText(Integer.toString(index));
402 }
403
404 /**
405 * Update the navigation buttons "enabled" state according to the current
406 * index value.
407 */
408 private synchronized void updateEnabled() {
409 first.setEnabled(index > min);
410 previous.setEnabled(index > min);
411 next.setEnabled(index < max || max == -1);
412 last.setEnabled(index < max || max == -1);
413 }
414 }