code cleanup
[fanfix.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderSearch.java
1 package be.nikiroo.fanfix.reader.ui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Component;
6 import java.awt.EventQueue;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.io.IOException;
10 import java.lang.reflect.InvocationTargetException;
11 import java.util.ArrayList;
12 import java.util.List;
13
14 import javax.swing.BoxLayout;
15 import javax.swing.JButton;
16 import javax.swing.JComboBox;
17 import javax.swing.JFrame;
18 import javax.swing.JLabel;
19 import javax.swing.JList;
20 import javax.swing.JPanel;
21 import javax.swing.JScrollPane;
22 import javax.swing.JTabbedPane;
23 import javax.swing.JTextField;
24 import javax.swing.ListCellRenderer;
25
26 import be.nikiroo.fanfix.Instance;
27 import be.nikiroo.fanfix.data.MetaData;
28 import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener;
29 import be.nikiroo.fanfix.searchable.BasicSearchable;
30 import be.nikiroo.fanfix.searchable.SearchableTag;
31 import be.nikiroo.fanfix.supported.SupportType;
32
33 /**
34 * This frame will allow you to search through the supported websites for new
35 * stories/comics.
36 *
37 * @author niki
38 */
39 // JCombobox<E> not 1.6 compatible
40 @SuppressWarnings({ "unchecked", "rawtypes" })
41 public class GuiReaderSearch extends JFrame {
42 private static final long serialVersionUID = 1L;
43
44 private List<SupportType> supportTypes;
45 private SupportType supportType;
46 private boolean searchByTags;
47 private String keywords;
48 private int page;
49 private int maxPage;
50
51 private JPanel tagBars;
52 private List<JComboBox> combos;
53
54 private JComboBox comboSupportTypes;
55 private JTabbedPane searchTabs;
56 private JTextField keywordsField;
57 private JButton submitKeywords;
58
59 private boolean seeWordcount;
60 private GuiReaderGroup books;
61
62 public GuiReaderSearch(final GuiReader reader) {
63 super("Browse stories");
64 setLayout(new BorderLayout());
65 setSize(800, 600);
66
67 page = 1; // TODO
68 maxPage = -1;
69 searchByTags = false;
70
71 supportTypes = new ArrayList<SupportType>();
72 for (SupportType type : SupportType.values()) {
73 if (BasicSearchable.getSearchable(type) != null) {
74 supportTypes.add(type);
75 }
76 }
77 supportType = supportTypes.isEmpty() ? null : supportTypes.get(0);
78
79 comboSupportTypes = new JComboBox(
80 supportTypes.toArray(new SupportType[] {}));
81 comboSupportTypes.addActionListener(new ActionListener() {
82 @Override
83 public void actionPerformed(ActionEvent e) {
84 updateSupportType((SupportType) comboSupportTypes
85 .getSelectedItem());
86 }
87 });
88 JPanel searchSites = new JPanel(new BorderLayout());
89 searchSites.add(comboSupportTypes, BorderLayout.CENTER);
90 searchSites.add(new JLabel(" " + "Website : "), BorderLayout.WEST);
91
92 searchTabs = new JTabbedPane();
93 searchTabs.addTab("By name", createByNameSearchPanel());
94 searchTabs.addTab("By tags", createByTagSearchPanel());
95
96 JPanel top = new JPanel(new BorderLayout());
97 top.add(searchSites, BorderLayout.NORTH);
98 top.add(searchTabs, BorderLayout.CENTER);
99
100 add(top, BorderLayout.NORTH);
101
102 books = new GuiReaderGroup(reader, null, null);
103 books.setActionListener(new BookActionListener() {
104 @Override
105 public void select(GuiReaderBook book) {
106 }
107
108 @Override
109 public void popupRequested(GuiReaderBook book, Component target,
110 int x, int y) {
111 }
112
113 @Override
114 public void action(GuiReaderBook book) {
115 new GuiReaderSearchAction(reader.getLibrary(), book.getInfo())
116 .setVisible(true);
117 }
118 });
119 JScrollPane scroll = new JScrollPane(books);
120 scroll.getVerticalScrollBar().setUnitIncrement(16);
121 add(scroll, BorderLayout.CENTER);
122
123 updateTags(null);
124 }
125
126 private JPanel createByNameSearchPanel() {
127 JPanel byName = new JPanel(new BorderLayout());
128
129 keywordsField = new JTextField();
130 byName.add(keywordsField, BorderLayout.CENTER);
131
132 submitKeywords = new JButton("Search");
133 byName.add(submitKeywords, BorderLayout.EAST);
134
135 // TODO: ENTER -> search
136
137 submitKeywords.addActionListener(new ActionListener() {
138 @Override
139 public void actionPerformed(ActionEvent e) {
140 search(supportType, keywordsField.getText(), page, 0);
141 }
142 });
143
144 return byName;
145 }
146
147 private JPanel createByTagSearchPanel() {
148 combos = new ArrayList<JComboBox>();
149
150 JPanel byTag = new JPanel();
151 tagBars = new JPanel();
152 tagBars.setLayout(new BoxLayout(tagBars, BoxLayout.Y_AXIS));
153 byTag.add(tagBars, BorderLayout.NORTH);
154
155 return byTag;
156 }
157
158 private void updateSupportType(SupportType supportType) {
159 if (supportType != this.supportType) {
160 this.supportType = supportType;
161 comboSupportTypes.setSelectedItem(supportType);
162 books.clear();
163 updateTags(null);
164 }
165 }
166
167 private void updateSearchBy(final boolean byTag) {
168 if (byTag != this.searchByTags) {
169 inUi(new Runnable() {
170 @Override
171 public void run() {
172 if (!byTag) {
173 searchTabs.setSelectedIndex(0);
174 } else {
175 searchTabs.setSelectedIndex(1);
176 }
177 }
178 });
179 }
180 }
181
182 private void updatePages(final int page, final Integer maxPage) {
183 inUi(new Runnable() {
184 @Override
185 public void run() {
186 GuiReaderSearch.this.page = page;
187 GuiReaderSearch.this.maxPage = maxPage;
188 // TODO: gui
189 System.out.println("page: " + page);
190 System.out.println("max page: " + maxPage);
191 }
192 });
193 }
194
195 // cannot be NULL
196 private void updateKeywords(final String keywords) {
197 if (!keywords.equals(this.keywords)) {
198 inUi(new Runnable() {
199 @Override
200 public void run() {
201 GuiReaderSearch.this.keywords = keywords;
202 keywordsField.setText(keywords);
203 }
204 });
205 }
206 }
207
208 // update and reset the tagsbar
209 // can be NULL, for base tags
210 private void updateTags(final SearchableTag tag) {
211 final List<SearchableTag> parents = new ArrayList<SearchableTag>();
212 SearchableTag parent = (tag == null) ? null : tag;
213 while (parent != null) {
214 parents.add(parent);
215 parent = parent.getParent();
216 }
217
218 inUi(new Runnable() {
219 @Override
220 public void run() {
221 tagBars.invalidate();
222 tagBars.removeAll();
223
224 // TODO: Slow UI
225 // TODO: select the right one
226 try {
227 SearchableTag selectedChild = parents.isEmpty() ? null
228 : parents.get(parents.size() - 1);
229 addTagBar(BasicSearchable.getSearchable(supportType)
230 .getTags(), selectedChild);
231 } catch (IOException e) {
232 error(e);
233 }
234
235 for (int i = parents.size() - 1; i >= 0; i--) {
236 SearchableTag selectedChild = null;
237 if (i > 0) {
238 selectedChild = parents.get(i - 1);
239 }
240 SearchableTag parent = parents.get(i);
241 addTagBar(parent.getChildren(), selectedChild);
242 }
243
244 tagBars.validate();
245 }
246 });
247 }
248
249 private void updateBooks(final List<GuiReaderBookInfo> infos) {
250 setWaitingScreen(true);
251 inUi(new Runnable() {
252 @Override
253 public void run() {
254 books.refreshBooks(infos, seeWordcount);
255 setWaitingScreen(false);
256 }
257 });
258 }
259
260 private void addTagBar(List<SearchableTag> tags,
261 final SearchableTag selected) {
262 tags.add(0, null);
263
264 final int comboIndex = combos.size();
265
266 final JComboBox combo = new JComboBox(
267 tags.toArray(new SearchableTag[] {}));
268 combo.setSelectedItem(selected);
269
270 final ListCellRenderer basic = combo.getRenderer();
271
272 combo.setRenderer(new ListCellRenderer() {
273 @Override
274 public Component getListCellRendererComponent(JList list,
275 Object value, int index, boolean isSelected,
276 boolean cellHasFocus) {
277
278 Object displayValue = value;
279 if (value instanceof SearchableTag) {
280 displayValue = ((SearchableTag) value).getName();
281 } else {
282 displayValue = "Select a tag...";
283 cellHasFocus = false;
284 isSelected = false;
285 }
286
287 Component rep = basic.getListCellRendererComponent(list,
288 displayValue, index, isSelected, cellHasFocus);
289
290 if (value == null) {
291 rep.setForeground(Color.GRAY);
292 }
293
294 return rep;
295 }
296 });
297
298 combo.addActionListener(new ActionListener() {
299 @Override
300 public void actionPerformed(ActionEvent e) {
301 final SearchableTag tag = (SearchableTag) combo
302 .getSelectedItem();
303 if (tag != null) {
304 while (comboIndex + 1 < combos.size()) {
305 JComboBox combo = combos.remove(comboIndex + 1);
306 tagBars.remove(combo);
307 }
308
309 addTagBar(tag, new Runnable() {
310 @Override
311 public void run() {
312 // TODO: slow ui
313 SearchableTag tag = ((SearchableTag) combo
314 .getSelectedItem());
315 if (tag != null && tag.isLeaf()) {
316 BasicSearchable searchable = BasicSearchable
317 .getSearchable(supportType);
318 List<MetaData> metas = new ArrayList<MetaData>();
319 try {
320 metas = searchable.search(tag, 1);
321 search(metas, 1,
322 searchable.searchPages(tag), 0);
323 } catch (IOException e) {
324 error(e);
325 }
326 }
327
328 setWaitingScreen(false);
329 }
330 });
331 }
332 }
333 });
334
335 combos.add(combo);
336 tagBars.add(combo);
337 }
338
339 // async, add children of tag, NULL = base tags
340 private void addTagBar(final SearchableTag tag, final Runnable inUi) {
341 new Thread(new Runnable() {
342 @Override
343 public void run() {
344 BasicSearchable searchable = BasicSearchable
345 .getSearchable(supportType);
346
347 List<SearchableTag> children = new ArrayList<SearchableTag>();
348 if (tag == null) {
349 try {
350 List<SearchableTag> baseTags = searchable.getTags();
351 children = baseTags;
352 } catch (IOException e) {
353 error(e);
354 }
355 } else {
356 try {
357 searchable.fillTag(tag);
358 } catch (IOException e) {
359 error(e);
360 }
361
362 if (!tag.isLeaf()) {
363 children = tag.getChildren();
364 } else {
365 children = null;
366 }
367 }
368
369 final List<SearchableTag> fchildren = children;
370 inUi(new Runnable() {
371 @Override
372 public void run() {
373 if (fchildren != null) {
374 addTagBar(fchildren, tag);
375 }
376
377 if (inUi != null) {
378 inUi.run();
379 }
380 }
381 });
382 }
383 }).start();
384 }
385
386 // item 0 = no selection, else = default selection
387 public void search(final SupportType searchOn, final String keywords,
388 final int page, final int item) {
389
390 setWaitingScreen(true);
391
392 updateSupportType(searchOn);
393 updateSearchBy(false);
394 updateKeywords(keywords);
395 updatePages(page, maxPage);
396
397 new Thread(new Runnable() {
398 @Override
399 public void run() {
400 BasicSearchable search = BasicSearchable
401 .getSearchable(searchOn);
402
403 int maxPage = -1;
404 try {
405 maxPage = search.searchPages(keywords);
406 } catch (IOException e) {
407 error(e);
408 }
409
410 if (page <= 0) {
411 updateBooks(new ArrayList<GuiReaderBookInfo>());
412 updatePages(0, maxPage);
413 } else {
414 List<MetaData> results;
415 try {
416 results = search.search(keywords, page);
417 } catch (IOException e) {
418 error(e);
419 results = new ArrayList<MetaData>();
420 }
421
422 search(results, page, maxPage, item);
423
424 // ! 1-based index !
425 if (item > 0 && item <= books.getBooksCount()) {
426 // TODO: "click" on item ITEM
427 }
428 }
429
430 setWaitingScreen(false);
431 }
432 }).start();
433 }
434
435 // tag: null = base tags
436 public void searchTag(final SupportType searchOn, final int page,
437 final int item, final SearchableTag tag) {
438
439 setWaitingScreen(true);
440
441 updateSupportType(searchOn);
442 updateSearchBy(true);
443 updateTags(tag);
444 updatePages(page, maxPage);
445
446 new Thread(new Runnable() {
447 @Override
448 public void run() {
449 BasicSearchable search = BasicSearchable
450 .getSearchable(searchOn);
451
452 if (tag != null) {
453 try {
454 search.fillTag(tag);
455 } catch (IOException e) {
456 error(e);
457 }
458
459 int maxPage = 0;
460 try {
461 maxPage = search.searchPages(tag);
462 } catch (IOException e) {
463 error(e);
464 }
465
466 updatePages(page, maxPage);
467
468 if (page > 0) {
469 List<MetaData> metas = new ArrayList<MetaData>();
470
471 if (tag.isLeaf()) {
472 try {
473 metas = search.search(tag, page);
474 } catch (IOException e) {
475 error(e);
476 }
477 } else {
478 List<SearchableTag> subtags = tag.getChildren();
479 if (item > 0 && item <= subtags.size()) {
480 SearchableTag subtag = subtags.get(item - 1);
481 try {
482 metas = search.search(subtag, page);
483 maxPage = subtag.getPages();
484 } catch (IOException e) {
485 error(e);
486 }
487 }
488 }
489
490 updatePages(page, maxPage);
491 search(metas, page, maxPage, item);
492 }
493 }
494
495 setWaitingScreen(false);
496 }
497 }).start();
498 }
499
500 // item 0 = no selection, else = default selection
501 public void search(final List<MetaData> results, final int page,
502 final int maxPage, final int item) {
503
504 updatePages(page, maxPage);
505
506 if (page <= 0) {
507 updateBooks(new ArrayList<GuiReaderBookInfo>());
508 updatePages(0, maxPage);
509 } else {
510 List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
511 for (MetaData meta : results) {
512 infos.add(GuiReaderBookInfo.fromMeta(meta));
513 }
514
515 updateBooks(infos);
516
517 // ! 1-based index !
518 if (item > 0 && item <= books.getBooksCount()) {
519 // TODO: "click" on item ITEM
520 }
521 }
522 }
523
524 /**
525 * Process the given action in the main Swing UI thread.
526 * <p>
527 * The code will make sure the current thread is the main UI thread and, if
528 * not, will switch to it before executing the runnable.
529 * <p>
530 * Synchronous operation.
531 *
532 * @param run
533 * the action to run
534 */
535 private void inUi(final Runnable run) {
536 if (EventQueue.isDispatchThread()) {
537 run.run();
538 } else {
539 try {
540 EventQueue.invokeAndWait(run);
541 } catch (InterruptedException e) {
542 error(e);
543 } catch (InvocationTargetException e) {
544 error(e);
545 }
546 }
547 }
548
549 private void error(Exception e) {
550 Instance.getTraceHandler().error(e);
551 }
552
553 private void setWaitingScreen(final boolean waiting) {
554 inUi(new Runnable() {
555 @Override
556 public void run() {
557 GuiReaderSearch.this.setEnabled(!waiting);
558 books.setEnabled(!waiting);
559 submitKeywords.setEnabled(!waiting);
560 }
561 });
562 }
563 }