Merge branch 'subtree'
[fanfix.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderNavBar.java
1 package be.nikiroo.fanfix.reader.ui;
2
3 import java.awt.Color;
4 import java.awt.LayoutManager;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.util.ArrayList;
8 import java.util.List;
9
10 import javax.swing.BoxLayout;
11 import javax.swing.JButton;
12 import javax.swing.JLabel;
13 import javax.swing.JPanel;
14
15 import be.nikiroo.fanfix.Instance;
16
17 /**
18 * A Swing-based navigation bar, that displays first/previous/next/last page
19 * buttons.
20 *
21 * @author niki
22 */
23 public class GuiReaderNavBar extends JPanel {
24 private static final long serialVersionUID = 1L;
25
26 private JLabel label;
27 private int index = 0;
28 private int min = 0;
29 private int max = 0;
30 private JButton[] navButtons;
31 String extraLabel = null;
32
33 private List<ActionListener> listeners = new ArrayList<ActionListener>();
34
35 /**
36 * Create a new navigation bar.
37 * <p>
38 * The minimum must be lower or equal to the maximum.
39 * <p>
40 * Note than a max of "-1" means "infinite".
41 *
42 * @param min
43 * the minimum page number (cannot be negative)
44 * @param max
45 * the maximum page number (cannot be lower than min, except if
46 * -1 (infinite))
47 *
48 * @throws IndexOutOfBoundsException
49 * if min &gt; max and max is not "-1"
50 */
51 public GuiReaderNavBar(int min, int max) {
52 if (min > max && max != -1) {
53 throw new IndexOutOfBoundsException(String.format(
54 "min (%d) > max (%d)", min, max));
55 }
56
57 LayoutManager layout = new BoxLayout(this, BoxLayout.X_AXIS);
58 setLayout(layout);
59
60 // TODO:
61 // JButton up = new BasicArrowButton(BasicArrowButton.NORTH);
62 // JButton down = new BasicArrowButton(BasicArrowButton.SOUTH);
63
64 navButtons = new JButton[4];
65
66 navButtons[0] = createNavButton("<<", new ActionListener() {
67 @Override
68 public void actionPerformed(ActionEvent e) {
69 setIndex(GuiReaderNavBar.this.min);
70 fireEvent();
71 }
72 });
73 navButtons[1] = createNavButton(" < ", new ActionListener() {
74 @Override
75 public void actionPerformed(ActionEvent e) {
76 setIndex(index - 1);
77 fireEvent();
78 }
79 });
80 navButtons[2] = createNavButton(" > ", new ActionListener() {
81 @Override
82 public void actionPerformed(ActionEvent e) {
83 setIndex(index + 1);
84 fireEvent();
85 }
86 });
87 navButtons[3] = createNavButton(">>", new ActionListener() {
88 @Override
89 public void actionPerformed(ActionEvent e) {
90 setIndex(GuiReaderNavBar.this.max);
91 fireEvent();
92 }
93 });
94
95 for (JButton navButton : navButtons) {
96 add(navButton);
97 }
98
99 label = new JLabel("");
100 add(label);
101
102 this.min = min;
103 this.max = max;
104 this.index = min;
105
106 updateEnabled();
107 updateLabel();
108 fireEvent();
109 }
110
111 /**
112 * The current index, must be between {@link GuiReaderNavBar#min} and
113 * {@link GuiReaderNavBar#max}, both inclusive.
114 *
115 * @return the index
116 */
117 public int getIndex() {
118 return index;
119 }
120
121 /**
122 * The current index, must be between {@link GuiReaderNavBar#min} and
123 * {@link GuiReaderNavBar#max}, both inclusive.
124 *
125 * @param index
126 * the new index
127 */
128 public void setIndex(int index) {
129 if (index != this.index) {
130 if (index < min || (index > max && max != -1)) {
131 throw new IndexOutOfBoundsException(String.format(
132 "Index %d but min/max is [%d/%d]", index, min, max));
133 }
134
135 this.index = index;
136 updateLabel();
137 }
138
139 updateEnabled();
140 }
141
142 /**
143 * The minimun page number. Cannot be negative.
144 *
145 * @return the min
146 */
147 public int getMin() {
148 return min;
149 }
150
151 /**
152 * The minimum page number. Cannot be negative.
153 * <p>
154 * May update the index if needed (if the index is &lt; the new min).
155 * <p>
156 * Will also (always) update the label and enable/disable the required
157 * buttons.
158 *
159 * @param min
160 * the new min
161 */
162 public void setMin(int min) {
163 this.min = min;
164 if (index < min) {
165 index = min;
166 }
167 updateEnabled();
168 updateLabel();
169
170 }
171
172 /**
173 * The maximum page number. Cannot be lower than min, except if -1
174 * (infinite).
175 *
176 * @return the max
177 */
178 public int getMax() {
179 return max;
180 }
181
182 /**
183 * The maximum page number. Cannot be lower than min, except if -1
184 * (infinite).
185 * <p>
186 * May update the index if needed (if the index is &gt; the new max).
187 * <p>
188 * Will also (always) update the label and enable/disable the required
189 * buttons.
190 *
191 * @param max
192 * the new max
193 */
194 public void setMax(int max) {
195 this.max = max;
196 if (index > max && max != -1) {
197 index = max;
198 }
199 updateEnabled();
200 updateLabel();
201 }
202
203 /**
204 * The current extra label to display with the default
205 * {@link GuiReaderNavBar#computeLabel(int, int, int)} implementation.
206 *
207 * @return the current label
208 */
209 public String getExtraLabel() {
210 return extraLabel;
211 }
212
213 /**
214 * The current extra label to display with the default
215 * {@link GuiReaderNavBar#computeLabel(int, int, int)} implementation.
216 *
217 * @param currentLabel
218 * the new current label
219 */
220 public void setExtraLabel(String currentLabel) {
221 this.extraLabel = currentLabel;
222 updateLabel();
223 }
224
225 /**
226 * Add a listener that will be called on each page change.
227 *
228 * @param listener
229 * the new listener
230 */
231 public void addActionListener(ActionListener listener) {
232 listeners.add(listener);
233 }
234
235 /**
236 * Remove the given listener if possible.
237 *
238 * @param listener
239 * the listener to remove
240 * @return TRUE if it was removed, FALSE if it was not found
241 */
242 public boolean removeActionListener(ActionListener listener) {
243 return listeners.remove(listener);
244 }
245
246 /**
247 * Remove all the listeners.
248 */
249 public void clearActionsListeners() {
250 listeners.clear();
251 }
252
253 /**
254 * Notify a change of page.
255 */
256 public void fireEvent() {
257 for (ActionListener listener : listeners) {
258 try {
259 listener.actionPerformed(new ActionEvent(this,
260 ActionEvent.ACTION_FIRST, "page changed"));
261 } catch (Exception e) {
262 Instance.getInstance().getTraceHandler().error(e);
263 }
264 }
265 }
266
267 /**
268 * Create a single navigation button.
269 *
270 * @param text
271 * the text to display
272 * @param action
273 * the action to take on click
274 * @return the button
275 */
276 private JButton createNavButton(String text, ActionListener action) {
277 JButton navButton = new JButton(text);
278 navButton.addActionListener(action);
279 navButton.setForeground(Color.BLUE);
280 return navButton;
281 }
282
283 /**
284 * Update the label displayed in the UI.
285 */
286 private void updateLabel() {
287 label.setText(computeLabel(index, min, max));
288 }
289
290 /**
291 * Update the navigation buttons "enabled" state according to the current
292 * index value.
293 */
294 private void updateEnabled() {
295 navButtons[0].setEnabled(index > min);
296 navButtons[1].setEnabled(index > min);
297 navButtons[2].setEnabled(index < max || max == -1);
298 navButtons[3].setEnabled(index < max || max == -1);
299 }
300
301 /**
302 * Return the label to display for the given index.
303 * <p>
304 * Swing HTML (HTML3) is supported if surrounded by &lt;HTML&gt; and
305 * &lt;/HTML&gt;.
306 * <p>
307 * By default, return "Page 1/5: current_label" (with the current index and
308 * {@link GuiReaderNavBar#getCurrentLabel()}).
309 *
310 * @param index
311 * the new index number
312 * @param mix
313 * the minimum index (inclusive)
314 * @param max
315 * the maximum index (inclusive)
316 * @return the label
317 */
318 protected String computeLabel(int index,
319 @SuppressWarnings("unused") int min, int max) {
320
321 String base = "&nbsp;&nbsp;<B>Page <SPAN COLOR='#444466'>%d</SPAN>&nbsp;";
322 if (max >= 0) {
323 base += "/&nbsp;%d";
324 }
325 base += "</B>";
326
327 String ifLabel = ": %s";
328
329 String display = base;
330 String label = getExtraLabel();
331 if (label != null && !label.trim().isEmpty()) {
332 display += ifLabel;
333 }
334
335 display = "<HTML>" + display + "</HTML>";
336
337 if (max >= 0) {
338 return String.format(display, index, max, label);
339 }
340
341 return String.format(display, index, label);
342 }
343 }