search: cleanup
[nikiroo-utils.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderGroup.java
CommitLineData
16a81ef7 1package be.nikiroo.fanfix.reader.ui;
4310bae9
NR
2
3import java.awt.BorderLayout;
4import java.awt.Color;
484a31aa 5import java.awt.Component;
4310bae9 6import java.awt.event.ActionListener;
07e0fc1e
NR
7import java.awt.event.ComponentAdapter;
8import java.awt.event.ComponentEvent;
17fafa56
NR
9import java.awt.event.FocusAdapter;
10import java.awt.event.FocusEvent;
07e0fc1e
NR
11import java.awt.event.KeyAdapter;
12import java.awt.event.KeyEvent;
4310bae9
NR
13import java.util.ArrayList;
14import java.util.List;
15
16import javax.swing.JLabel;
17import javax.swing.JPanel;
18
5bc9573b 19import be.nikiroo.fanfix.bundles.StringIdGui;
16a81ef7 20import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener;
4310bae9
NR
21import be.nikiroo.utils.ui.WrapLayout;
22
23/**
5dd985cf 24 * A group of {@link GuiReaderBook}s for display.
4310bae9
NR
25 *
26 * @author niki
27 */
5dd985cf 28public class GuiReaderGroup extends JPanel {
4310bae9
NR
29 private static final long serialVersionUID = 1L;
30 private BookActionListener action;
31 private Color backgroundColor;
5dd985cf 32 private GuiReader reader;
79a99506 33 private List<GuiReaderBookInfo> infos;
5dd985cf 34 private List<GuiReaderBook> books;
4310bae9 35 private JPanel pane;
e92e4ae3 36 private JLabel titleLabel;
793f1071 37 private boolean words; // words or authors (secondary info on books)
07e0fc1e 38 private int itemsPerLine;
4310bae9
NR
39
40 /**
5dd985cf 41 * Create a new {@link GuiReaderGroup}.
4310bae9
NR
42 *
43 * @param reader
e42573a0
NR
44 * the {@link GuiReaderBook} used to probe some information about
45 * the stories
4310bae9 46 * @param title
e92e4ae3
NR
47 * the title of this group (can be NULL for "no title", an empty
48 * {@link String} will trigger a default title for empty groups)
4310bae9
NR
49 * @param backgroundColor
50 * the background colour to use (or NULL for default)
51 */
e42573a0 52 public GuiReaderGroup(GuiReader reader, String title, Color backgroundColor) {
4310bae9
NR
53 this.reader = reader;
54 this.backgroundColor = backgroundColor;
55
56 this.pane = new JPanel();
57
58 pane.setLayout(new WrapLayout(WrapLayout.LEADING, 5, 5));
59 if (backgroundColor != null) {
60 pane.setBackground(backgroundColor);
61 setBackground(backgroundColor);
62 }
63
64 setLayout(new BorderLayout(0, 10));
17fafa56
NR
65
66 // Make it focusable:
67 setFocusable(true);
68 setEnabled(true);
69 setVisible(true);
70
4310bae9
NR
71 add(pane, BorderLayout.CENTER);
72
e92e4ae3
NR
73 titleLabel = new JLabel();
74 titleLabel.setHorizontalAlignment(JLabel.CENTER);
75 add(titleLabel, BorderLayout.NORTH);
76 setTitle(title);
07e0fc1e
NR
77
78 // Compute the number of items per line at each resize
79 addComponentListener(new ComponentAdapter() {
80 @Override
81 public void componentResized(ComponentEvent e) {
82 super.componentResized(e);
83 computeItemsPerLine();
84 }
85 });
86 computeItemsPerLine();
87
88 addKeyListener(new KeyAdapter() {
17fafa56
NR
89 @Override
90 public void keyPressed(KeyEvent e) {
91 onKeyPressed(e);
92 }
93
07e0fc1e
NR
94 @Override
95 public void keyTyped(KeyEvent e) {
96 onKeyTyped(e);
97 }
98 });
17fafa56
NR
99
100 addFocusListener(new FocusAdapter() {
101 @Override
102 public void focusGained(FocusEvent e) {
103 if (getSelectedBookIndex() < 0) {
104 setSelectedBook(0, true);
105 }
106 }
107
108 @Override
109 public void focusLost(FocusEvent e) {
110 setBackground(null);
111 setSelectedBook(-1, false);
112 }
113 });
07e0fc1e
NR
114 }
115
e92e4ae3
NR
116 /**
117 * The title of this group (can be NULL for "no title", an empty
118 * {@link String} will trigger a default title for empty groups)
119 *
120 * @param title
121 * the title or NULL
122 */
123 public void setTitle(String title) {
124 if (title != null) {
125 if (title.isEmpty()) {
126 title = GuiReader.trans(StringIdGui.MENU_AUTHORS_UNKNOWN);
127 }
128
129 titleLabel.setText(String.format("<html>"
130 + "<body style='text-align: center; color: gray;'><br><b>"
131 + "%s" + "</b></body>" + "</html>", title));
132 titleLabel.setVisible(true);
133 } else {
134 titleLabel.setVisible(false);
135 }
136 }
137
07e0fc1e
NR
138 /**
139 * Compute how many items can fit in a line so UP and DOWN can be used to go
140 * up/down one line at a time.
141 */
142 private void computeItemsPerLine() {
143 // TODO
144 itemsPerLine = 5;
4310bae9
NR
145 }
146
147 /**
148 * Set the {@link ActionListener} that will be fired on each
5dd985cf 149 * {@link GuiReaderBook} action.
4310bae9
NR
150 *
151 * @param action
152 * the action
153 */
154 public void setActionListener(BookActionListener action) {
155 this.action = action;
79a99506 156 refreshBooks(infos, words);
4310bae9
NR
157 }
158
159 /**
5dd985cf 160 * Refresh the list of {@link GuiReaderBook}s displayed in the control.
4310bae9 161 *
c349fd48
NR
162 * @param infos
163 * the new list of infos
793f1071
NR
164 * @param seeWordcount
165 * TRUE to see word counts, FALSE to see authors
4310bae9 166 */
fb1ffdd0
NR
167 public void refreshBooks(List<GuiReaderBookInfo> infos, boolean seeWordcount) {
168 this.infos = infos;
8590da19
NR
169 refreshBooks(seeWordcount);
170 }
171
172 /**
173 * Refresh the list of {@link GuiReaderBook}s displayed in the control.
174 * <p>
175 * Will not change the current stories.
176 *
177 * @param seeWordcount
178 * TRUE to see word counts, FALSE to see authors
179 */
180 public void refreshBooks(boolean seeWordcount) {
793f1071 181 this.words = seeWordcount;
4310bae9 182
5dd985cf 183 books = new ArrayList<GuiReaderBook>();
4310bae9
NR
184 invalidate();
185 pane.invalidate();
186 pane.removeAll();
187
fb1ffdd0
NR
188 if (infos != null) {
189 for (GuiReaderBookInfo info : infos) {
79a99506 190 boolean isCached = false;
b31a0db0 191 if (info.getMeta() != null && info.getMeta().getLuid() != null) {
79a99506
NR
192 isCached = reader.isCached(info.getMeta().getLuid());
193 }
194
195 GuiReaderBook book = new GuiReaderBook(reader, info, isCached,
fb1ffdd0 196 words);
4310bae9
NR
197 if (backgroundColor != null) {
198 book.setBackground(backgroundColor);
199 }
200
201 books.add(book);
202
203 book.addActionListener(new BookActionListener() {
211f7ddb 204 @Override
5dd985cf 205 public void select(GuiReaderBook book) {
17fafa56 206 GuiReaderGroup.this.requestFocusInWindow();
5dd985cf 207 for (GuiReaderBook abook : books) {
4310bae9
NR
208 abook.setSelected(abook == book);
209 }
210 }
211
211f7ddb 212 @Override
484a31aa
NR
213 public void popupRequested(GuiReaderBook book,
214 Component target, int x, int y) {
4310bae9
NR
215 }
216
211f7ddb 217 @Override
5dd985cf 218 public void action(GuiReaderBook book) {
4310bae9
NR
219 }
220 });
221
222 if (action != null) {
223 book.addActionListener(action);
224 }
225
226 pane.add(book);
227 }
228 }
229
230 pane.validate();
231 pane.repaint();
232 validate();
233 repaint();
234 }
235
236 /**
237 * Enables or disables this component, depending on the value of the
238 * parameter <code>b</code>. An enabled component can respond to user input
239 * and generate events. Components are enabled initially by default.
240 * <p>
241 * Disabling this component will also affect its children.
242 *
243 * @param b
244 * If <code>true</code>, this component is enabled; otherwise
245 * this component is disabled
246 */
247 @Override
248 public void setEnabled(boolean b) {
249 if (books != null) {
5dd985cf 250 for (GuiReaderBook book : books) {
4310bae9
NR
251 book.setEnabled(b);
252 book.repaint();
253 }
254 }
255
256 pane.setEnabled(b);
257 super.setEnabled(b);
258 repaint();
259 }
07e0fc1e 260
e92e4ae3
NR
261 /**
262 * The number of books in this group.
263 *
264 * @return the count
265 */
266 public int getBooksCount() {
267 return books.size();
268 }
269
17fafa56
NR
270 /**
271 * Return the index of the currently selected book if any, -1 if none.
272 *
273 * @return the index or -1
274 */
e92e4ae3 275 public int getSelectedBookIndex() {
17fafa56
NR
276 int index = -1;
277 for (int i = 0; i < books.size(); i++) {
278 if (books.get(i).isSelected()) {
279 index = i;
280 break;
281 }
282 }
283 return index;
284 }
285
286 /**
287 * Select the given book, or unselect all items.
288 *
289 * @param index
290 * the index of the book to select, can be outside the bounds
291 * (either all the items will be unselected or the first or last
292 * book will then be selected, see <tt>forceRange>/tt>)
293 * @param forceRange
294 * TRUE to constraint the index to the first/last element, FALSE
295 * to unselect when outside the range
296 */
e92e4ae3 297 public void setSelectedBook(int index, boolean forceRange) {
17fafa56
NR
298 int previousIndex = getSelectedBookIndex();
299
300 if (index >= books.size()) {
301 if (forceRange) {
302 index = books.size() - 1;
303 } else {
304 index = -1;
305 }
306 }
307
308 if (index < 0 && forceRange) {
309 index = 0;
310 }
311
312 if (previousIndex >= 0) {
313 books.get(previousIndex).setSelected(false);
314 }
315
e0fa20fe 316 if (index >= 0 && !books.isEmpty()) {
17fafa56
NR
317 books.get(index).setSelected(true);
318 }
319 }
320
07e0fc1e
NR
321 /**
322 * The action to execute when a key is typed.
323 *
324 * @param e
325 * the key event
326 */
327 private void onKeyTyped(KeyEvent e) {
328 boolean consumed = false;
484a31aa
NR
329 boolean action = e.getKeyChar() == '\n';
330 boolean popup = e.getKeyChar() == ' ';
331 if (action || popup) {
17fafa56
NR
332 consumed = true;
333
334 int index = getSelectedBookIndex();
335 if (index >= 0) {
484a31aa
NR
336 GuiReaderBook book = books.get(index);
337 if (action) {
338 book.action();
339 } else if (popup) {
340 book.popup(book, book.getWidth() / 2, book.getHeight() / 2);
341 }
17fafa56
NR
342 }
343 }
344
345 if (consumed) {
346 e.consume();
347 }
348 }
349
350 /**
351 * The action to execute when a key is pressed.
352 *
353 * @param e
354 * the key event
355 */
356 private void onKeyPressed(KeyEvent e) {
357 boolean consumed = false;
07e0fc1e
NR
358 if (e.isActionKey()) {
359 int offset = 0;
360 switch (e.getKeyCode()) {
361 case KeyEvent.VK_LEFT:
362 offset = -1;
363 break;
364 case KeyEvent.VK_RIGHT:
365 offset = 1;
366 break;
367 case KeyEvent.VK_UP:
17fafa56 368 offset = -itemsPerLine;
07e0fc1e
NR
369 break;
370 case KeyEvent.VK_DOWN:
17fafa56 371 offset = itemsPerLine;
07e0fc1e
NR
372 break;
373 }
374
375 if (offset != 0) {
376 consumed = true;
377
17fafa56
NR
378 int previousIndex = getSelectedBookIndex();
379 if (previousIndex >= 0) {
380 setSelectedBook(previousIndex + offset, true);
07e0fc1e
NR
381 }
382 }
383 }
384
385 if (consumed) {
386 e.consume();
07e0fc1e
NR
387 }
388 }
4310bae9 389}