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