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