Merge branch 'subtree'
[nikiroo-utils.git] / src / be / nikiroo / utils / ui / NavBar.java
1 package be.nikiroo.utils.ui;
2
3 import java.awt.Dimension;
4 import java.awt.LayoutManager;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7
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 maxPage;
28 private JLabel label;
29
30 private int index = 0;
31 private int min = 0;
32 private int max = 0;
33 private String extraLabel = null;
34
35 private JButton first;
36 private JButton previous;
37 private JButton next;
38 private JButton last;
39
40 /**
41 * Create a new navigation bar.
42 * <p>
43 * The minimum must be lower or equal to the maximum.
44 * <p>
45 * Note than a max of "-1" means "infinite".
46 *
47 * @param min
48 * the minimum page number (cannot be negative)
49 * @param max
50 * the maximum page number (cannot be lower than min, except if
51 * -1 (infinite))
52 *
53 * @throws IndexOutOfBoundsException
54 * if min &gt; max and max is not "-1"
55 */
56 public NavBar(int min, int max) {
57 if (min > max && max != -1) {
58 throw new IndexOutOfBoundsException(
59 String.format("min (%d) > max (%d)", min, max));
60 }
61
62 LayoutManager layout = new BoxLayout(this, BoxLayout.X_AXIS);
63 setLayout(layout);
64
65 // Page navigation
66 first = new JButton();
67 first.addActionListener(new ActionListener() {
68 @Override
69 public void actionPerformed(ActionEvent e) {
70 first();
71 }
72 });
73
74 previous = new JButton();
75 previous.addActionListener(new ActionListener() {
76 @Override
77 public void actionPerformed(ActionEvent e) {
78 previous();
79 }
80 });
81
82 page = new JTextField(Integer.toString(min));
83 page.setPreferredSize(
84 new Dimension(new JButton("1234").getPreferredSize().width,
85 new JButton("dummy").getPreferredSize().height));
86 page.setMaximumSize(new Dimension(Integer.MAX_VALUE,
87 page.getPreferredSize().height));
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 maxPage = new JLabel("of " + max);
106
107 next = new JButton();
108 next.addActionListener(new ActionListener() {
109 @Override
110 public void actionPerformed(ActionEvent e) {
111 next();
112 }
113 });
114
115 last = new JButton();
116 last.addActionListener(new ActionListener() {
117 @Override
118 public void actionPerformed(ActionEvent e) {
119 last();
120 }
121 });
122
123 // Set the << < > >> "icons"
124 setIcons(null, null, null, null);
125
126 this.add(first);
127 this.add(previous);
128 this.add(new JLabel(" "));
129 this.add(page);
130 this.add(new JLabel(" "));
131 this.add(maxPage);
132 this.add(new JLabel(" "));
133 this.add(next);
134 this.add(last);
135
136 this.add(label = new JLabel(""));
137
138 this.min = min;
139 this.max = max;
140 this.index = min;
141
142 updateEnabled();
143 updateLabel();
144 fireActionPerformed(PAGE_CHANGED);
145 }
146
147 /**
148 * The current index, must be between {@link NavBar#min} and
149 * {@link NavBar#max}, both inclusive.
150 *
151 * @return the index
152 */
153 public int getIndex() {
154 return index;
155 }
156
157 /**
158 * The current index, should be between {@link NavBar#min} and
159 * {@link NavBar#max}, both inclusive.
160 *
161 * @param index
162 * the new index
163 *
164 * @return TRUE if the index changed, FALSE if not (either it was already at
165 * that value, or it is outside of the bounds set by
166 * {@link NavBar#min} and {@link NavBar#max})
167 */
168 public synchronized boolean setIndex(int index) {
169 if (index != this.index) {
170 if (index < min || (index > max && max != -1)) {
171 return false;
172 }
173
174 this.index = index;
175 updateLabel();
176 updateEnabled();
177
178 return true;
179 }
180
181 return false;
182 }
183
184 /**
185 * The minimun page number. Cannot be negative.
186 *
187 * @return the min
188 */
189 public int getMin() {
190 return min;
191 }
192
193 /**
194 * The minimum page number. Cannot be negative.
195 * <p>
196 * May update the index if needed (if the index is &lt; the new min).
197 * <p>
198 * Will also (always) update the label and enable/disable the required
199 * buttons.
200 *
201 * @param min
202 * the new min
203 */
204 public synchronized void setMin(int min) {
205 this.min = min;
206 if (index < min) {
207 index = min;
208 }
209
210 updateEnabled();
211 updateLabel();
212 }
213
214 /**
215 * The maximum page number. Cannot be lower than min, except if -1
216 * (infinite).
217 *
218 * @return the max
219 */
220 public int getMax() {
221 return max;
222 }
223
224 /**
225 * The maximum page number. Cannot be lower than min, except if -1
226 * (infinite).
227 * <p>
228 * May update the index if needed (if the index is &gt; the new max).
229 * <p>
230 * Will also (always) update the label and enable/disable the required
231 * buttons.
232 *
233 * @param max
234 * the new max
235 */
236 public synchronized void setMax(int max) {
237 this.max = max;
238 if (index > max && max != -1) {
239 index = max;
240 }
241
242 maxPage.setText("of " + max);
243 updateEnabled();
244 updateLabel();
245 }
246
247 /**
248 * The current extra label to display.
249 *
250 * @return the current label
251 */
252 public String getExtraLabel() {
253 return extraLabel;
254 }
255
256 /**
257 * The current extra label to display.
258 *
259 * @param currentLabel
260 * the new current label
261 */
262 public void setExtraLabel(String currentLabel) {
263 this.extraLabel = currentLabel;
264 updateLabel();
265 }
266
267 /**
268 * Change the page to the next one.
269 *
270 * @return TRUE if it changed
271 */
272 public synchronized boolean next() {
273 if (setIndex(index + 1)) {
274 fireActionPerformed(PAGE_CHANGED);
275 return true;
276 }
277
278 return false;
279 }
280
281 /**
282 * Change the page to the previous one.
283 *
284 * @return TRUE if it changed
285 */
286 public synchronized boolean previous() {
287 if (setIndex(index - 1)) {
288 fireActionPerformed(PAGE_CHANGED);
289 return true;
290 }
291
292 return false;
293 }
294
295 /**
296 * Change the page to the first one.
297 *
298 * @return TRUE if it changed
299 */
300 public synchronized boolean first() {
301 if (setIndex(min)) {
302 fireActionPerformed(PAGE_CHANGED);
303 return true;
304 }
305
306 return false;
307 }
308
309 /**
310 * Change the page to the last one.
311 *
312 * @return TRUE if it changed
313 */
314 public synchronized boolean last() {
315 if (setIndex(max)) {
316 fireActionPerformed(PAGE_CHANGED);
317 return true;
318 }
319
320 return false;
321 }
322
323 /**
324 * Set icons for the buttons instead of square brackets.
325 * <p>
326 * Any NULL value will make the button use square brackets again.
327 *
328 * @param first
329 * the icon of the button "go to first page"
330 * @param previous
331 * the icon of the button "go to previous page"
332 * @param next
333 * the icon of the button "go to next page"
334 * @param last
335 * the icon of the button "go to last page"
336 */
337 public void setIcons(Icon first, Icon previous, Icon next, Icon last) {
338 this.first.setIcon(first);
339 this.first.setText(first == null ? "<<" : "");
340 this.previous.setIcon(previous);
341 this.previous.setText(previous == null ? "<" : "");
342 this.next.setIcon(next);
343 this.next.setText(next == null ? ">" : "");
344 this.last.setIcon(last);
345 this.last.setText(last == null ? ">>" : "");
346 }
347
348 /**
349 * Update the label displayed in the UI.
350 */
351 private void updateLabel() {
352 label.setText(getExtraLabel());
353 page.setText(Integer.toString(index));
354 }
355
356 /**
357 * Update the navigation buttons "enabled" state according to the current
358 * index value.
359 */
360 private synchronized void updateEnabled() {
361 first.setEnabled(index > min);
362 previous.setEnabled(index > min);
363 next.setEnabled(index < max || max == -1);
364 last.setEnabled(index < max || max == -1);
365 }
366 }