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