Navbar: do not throw when next() cannot go further
[fanfix.git] / 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(new Dimension(page.getPreferredSize().width * 2,
84 page.getPreferredSize().height));
85 page.addActionListener(new ActionListener() {
86 @Override
87 public void actionPerformed(ActionEvent e) {
88 try {
89 int pageNb = Integer.parseInt(page.getText());
90 if (pageNb < NavBar.this.min || pageNb > NavBar.this.max) {
91 throw new NumberFormatException("invalid");
92 }
93
94 if (setIndex(pageNb))
95 fireActionPerformed(PAGE_CHANGED);
96 } catch (NumberFormatException nfe) {
97 page.setText(Integer.toString(index));
98 }
99 }
100 });
101
102 maxPage = new JLabel(" of " + max + " ");
103
104 next = new JButton();
105 next.addActionListener(new ActionListener() {
106 @Override
107 public void actionPerformed(ActionEvent e) {
108 next();
109 }
110 });
111
112 last = new JButton();
113 last.addActionListener(new ActionListener() {
114 @Override
115 public void actionPerformed(ActionEvent e) {
116 last();
117 }
118 });
119
120 // Set the << < > >> "icons"
121 setIcons(null, null, null, null);
122
123 this.add(first);
124 this.add(previous);
125 this.add(page);
126 this.add(maxPage);
127 this.add(next);
128 this.add(last);
129
130 label = new JLabel("");
131 this.add(label);
132
133 this.min = min;
134 this.max = max;
135 this.index = min;
136
137 updateEnabled();
138 updateLabel();
139 fireActionPerformed(PAGE_CHANGED);
140 }
141
142 /**
143 * The current index, must be between {@link NavBar#min} and
144 * {@link NavBar#max}, both inclusive.
145 *
146 * @return the index
147 */
148 public int getIndex() {
149 return index;
150 }
151
152 /**
153 * The current index, should be between {@link NavBar#min} and
154 * {@link NavBar#max}, both inclusive.
155 *
156 * @param index
157 * the new index
158 *
159 * @return TRUE if the index changed, FALSE if not (either it was already at
160 * that value, or it is outside of the bounds set by
161 * {@link NavBar#min} and {@link NavBar#max})
162 */
163 public synchronized boolean setIndex(int index) {
164 if (index != this.index) {
165 if (index < min || (index > max && max != -1)) {
166 return false;
167 }
168
169 this.index = index;
170 updateLabel();
171 updateEnabled();
172
173 return true;
174 }
175
176 return false;
177 }
178
179 /**
180 * The minimun page number. Cannot be negative.
181 *
182 * @return the min
183 */
184 public int getMin() {
185 return min;
186 }
187
188 /**
189 * The minimum page number. Cannot be negative.
190 * <p>
191 * May update the index if needed (if the index is &lt; the new min).
192 * <p>
193 * Will also (always) update the label and enable/disable the required
194 * buttons.
195 *
196 * @param min
197 * the new min
198 */
199 public synchronized void setMin(int min) {
200 this.min = min;
201 if (index < min) {
202 index = min;
203 }
204
205 updateEnabled();
206 updateLabel();
207 }
208
209 /**
210 * The maximum page number. Cannot be lower than min, except if -1
211 * (infinite).
212 *
213 * @return the max
214 */
215 public int getMax() {
216 return max;
217 }
218
219 /**
220 * The maximum page number. Cannot be lower than min, except if -1
221 * (infinite).
222 * <p>
223 * May update the index if needed (if the index is &gt; the new max).
224 * <p>
225 * Will also (always) update the label and enable/disable the required
226 * buttons.
227 *
228 * @param max
229 * the new max
230 */
231 public synchronized void setMax(int max) {
232 this.max = max;
233 if (index > max && max != -1) {
234 index = max;
235 }
236
237 maxPage.setText(" of " + max + " ");
238 updateEnabled();
239 updateLabel();
240 }
241
242 /**
243 * The current extra label to display.
244 *
245 * @return the current label
246 */
247 public String getExtraLabel() {
248 return extraLabel;
249 }
250
251 /**
252 * The current extra label to display.
253 *
254 * @param currentLabel
255 * the new current label
256 */
257 public void setExtraLabel(String currentLabel) {
258 this.extraLabel = currentLabel;
259 updateLabel();
260 }
261
262 /**
263 * Change the page to the next one.
264 *
265 * @return TRUE if it changed
266 */
267 public synchronized boolean next() {
268 if (setIndex(index + 1)) {
269 fireActionPerformed(PAGE_CHANGED);
270 return true;
271 }
272
273 return false;
274 }
275
276 /**
277 * Change the page to the previous one.
278 *
279 * @return TRUE if it changed
280 */
281 public synchronized boolean previous() {
282 if (setIndex(index - 1)) {
283 fireActionPerformed(PAGE_CHANGED);
284 return true;
285 }
286
287 return false;
288 }
289
290 /**
291 * Change the page to the first one.
292 *
293 * @return TRUE if it changed
294 */
295 public synchronized boolean first() {
296 if (setIndex(min)) {
297 fireActionPerformed(PAGE_CHANGED);
298 return true;
299 }
300
301 return false;
302 }
303
304 /**
305 * Change the page to the last one.
306 *
307 * @return TRUE if it changed
308 */
309 public synchronized boolean last() {
310 if (setIndex(max)) {
311 fireActionPerformed(PAGE_CHANGED);
312 return true;
313 }
314
315 return false;
316 }
317
318 /**
319 * Set icons for the buttons instead of square brackets.
320 * <p>
321 * Any NULL value will make the button use square brackets again.
322 *
323 * @param first
324 * the icon of the button "go to first page"
325 * @param previous
326 * the icon of the button "go to previous page"
327 * @param next
328 * the icon of the button "go to next page"
329 * @param last
330 * the icon of the button "go to last page"
331 */
332 public void setIcons(Icon first, Icon previous, Icon next, Icon last) {
333 this.first.setIcon(first);
334 this.first.setText(first == null ? "<<" : "");
335 this.previous.setIcon(previous);
336 this.previous.setText(previous == null ? "<" : "");
337 this.next.setIcon(next);
338 this.next.setText(next == null ? ">" : "");
339 this.last.setIcon(last);
340 this.last.setText(last == null ? ">>" : "");
341 }
342
343 /**
344 * Update the label displayed in the UI.
345 */
346 private void updateLabel() {
347 label.setText(getExtraLabel());
348 page.setText(Integer.toString(index));
349 }
350
351 /**
352 * Update the navigation buttons "enabled" state according to the current
353 * index value.
354 */
355 private synchronized void updateEnabled() {
356 first.setEnabled(index > min);
357 previous.setEnabled(index > min);
358 next.setEnabled(index < max || max == -1);
359 last.setEnabled(index < max || max == -1);
360 }
361 }