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