package be.nikiroo.fanfix.reader.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.ListCellRenderer; import be.nikiroo.fanfix.Instance; import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener; import be.nikiroo.fanfix.searchable.BasicSearchable; import be.nikiroo.fanfix.searchable.SearchableTag; import be.nikiroo.fanfix.supported.SupportType; /** * This frame will allow you to search through the supported websites for new * stories/comics. * * @author niki */ // JCombobox not 1.6 compatible @SuppressWarnings({ "unchecked", "rawtypes" }) public class GuiReaderSearchFrame extends JFrame { private static final long serialVersionUID = 1L; private List supportTypes; private SupportType supportType; private boolean searchByTags; private String keywords; private int page; private int maxPage; private JPanel tagBars; private List combos; private JComboBox comboSupportTypes; private JTabbedPane searchTabs; private JTextField keywordsField; private JButton submitKeywords; private boolean seeWordcount; private GuiReaderGroup books; public GuiReaderSearchFrame(final GuiReader reader) { super("Browse stories"); setLayout(new BorderLayout()); setSize(800, 600); page = 1; // TODO maxPage = -1; searchByTags = false; supportTypes = new ArrayList(); for (SupportType type : SupportType.values()) { if (BasicSearchable.getSearchable(type) != null) { supportTypes.add(type); } } supportType = supportTypes.isEmpty() ? null : supportTypes.get(0); comboSupportTypes = new JComboBox( supportTypes.toArray(new SupportType[] {})); comboSupportTypes.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateSupportType((SupportType) comboSupportTypes .getSelectedItem()); } }); JPanel searchSites = new JPanel(new BorderLayout()); searchSites.add(comboSupportTypes, BorderLayout.CENTER); searchSites.add(new JLabel(" " + "Website : "), BorderLayout.WEST); searchTabs = new JTabbedPane(); searchTabs.addTab("By name", createByNameSearchPanel()); searchTabs.addTab("By tags", createByTagSearchPanel()); JPanel top = new JPanel(new BorderLayout()); top.add(searchSites, BorderLayout.NORTH); top.add(searchTabs, BorderLayout.CENTER); add(top, BorderLayout.NORTH); books = new GuiReaderGroup(reader, null, null); books.setActionListener(new BookActionListener() { @Override public void select(GuiReaderBook book) { } @Override public void popupRequested(GuiReaderBook book, Component target, int x, int y) { } @Override public void action(GuiReaderBook book) { new GuiReaderSearchAction(reader.getLibrary(), book.getInfo()) .setVisible(true); } }); JScrollPane scroll = new JScrollPane(books); scroll.getVerticalScrollBar().setUnitIncrement(16); add(scroll, BorderLayout.CENTER); updateTags(null); } private JPanel createByNameSearchPanel() { JPanel byName = new JPanel(new BorderLayout()); keywordsField = new JTextField(); byName.add(keywordsField, BorderLayout.CENTER); submitKeywords = new JButton("Search"); byName.add(submitKeywords, BorderLayout.EAST); // TODO: ENTER -> search submitKeywords.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { search(supportType, keywordsField.getText(), page, 0); } }); return byName; } private JPanel createByTagSearchPanel() { combos = new ArrayList(); JPanel byTag = new JPanel(); tagBars = new JPanel(); tagBars.setLayout(new BoxLayout(tagBars, BoxLayout.Y_AXIS)); byTag.add(tagBars, BorderLayout.NORTH); return byTag; } private void updateSupportType(SupportType supportType) { if (supportType != this.supportType) { this.supportType = supportType; comboSupportTypes.setSelectedItem(supportType); books.clear(); updateTags(null); } } private void updateSearchBy(final boolean byTag) { if (byTag != this.searchByTags) { inUi(new Runnable() { @Override public void run() { if (!byTag) { searchTabs.setSelectedIndex(0); } else { searchTabs.setSelectedIndex(1); } } }); } } private void updatePages(final int page, final Integer maxPage) { inUi(new Runnable() { @Override public void run() { GuiReaderSearchFrame.this.page = page; GuiReaderSearchFrame.this.maxPage = maxPage; // TODO: gui System.out.println("page: " + page); System.out.println("max page: " + maxPage); } }); } // cannot be NULL private void updateKeywords(final String keywords) { if (!keywords.equals(this.keywords)) { inUi(new Runnable() { @Override public void run() { GuiReaderSearchFrame.this.keywords = keywords; keywordsField.setText(keywords); } }); } } // update and reset the tagsbar // can be NULL, for base tags private void updateTags(final SearchableTag tag) { final List parents = new ArrayList(); SearchableTag parent = (tag == null) ? null : tag; while (parent != null) { parents.add(parent); parent = parent.getParent(); } inUi(new Runnable() { @Override public void run() { tagBars.invalidate(); tagBars.removeAll(); // TODO: Slow UI // TODO: select the right one try { SearchableTag selectedChild = parents.isEmpty() ? null : parents.get(parents.size() - 1); addTagBar(BasicSearchable.getSearchable(supportType) .getTags(), selectedChild); } catch (IOException e) { error(e); } for (int i = parents.size() - 1; i >= 0; i--) { SearchableTag selectedChild = null; if (i > 0) { selectedChild = parents.get(i - 1); } SearchableTag parent = parents.get(i); addTagBar(parent.getChildren(), selectedChild); } tagBars.validate(); } }); } private void updateBooks(final List infos) { setWaitingScreen(true); inUi(new Runnable() { @Override public void run() { books.refreshBooks(infos, seeWordcount); setWaitingScreen(false); } }); } private void addTagBar(List tags, final SearchableTag selected) { tags.add(0, null); final int comboIndex = combos.size(); final JComboBox combo = new JComboBox( tags.toArray(new SearchableTag[] {})); combo.setSelectedItem(selected); final ListCellRenderer basic = combo.getRenderer(); combo.setRenderer(new ListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Object displayValue = value; if (value instanceof SearchableTag) { displayValue = ((SearchableTag) value).getName(); } else { displayValue = "Select a tag..."; cellHasFocus = false; isSelected = false; } Component rep = basic.getListCellRendererComponent(list, displayValue, index, isSelected, cellHasFocus); if (value == null) { rep.setForeground(Color.GRAY); } return rep; } }); combo.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final SearchableTag tag = (SearchableTag) combo .getSelectedItem(); if (tag != null) { while (comboIndex + 1 < combos.size()) { JComboBox combo = combos.remove(comboIndex + 1); tagBars.remove(combo); } addTagBar(tag, new Runnable() { @Override public void run() { // TODO: slow ui SearchableTag tag = ((SearchableTag) combo .getSelectedItem()); if (tag != null && tag.isLeaf()) { BasicSearchable searchable = BasicSearchable .getSearchable(supportType); List metas = new ArrayList(); try { metas = searchable.search(tag, 1); search(metas, 1, searchable.searchPages(tag), 0); } catch (IOException e) { error(e); } } setWaitingScreen(false); } }); } } }); combos.add(combo); tagBars.add(combo); } // async, add children of tag, NULL = base tags private void addTagBar(final SearchableTag tag, final Runnable inUi) { new Thread(new Runnable() { @Override public void run() { BasicSearchable searchable = BasicSearchable .getSearchable(supportType); List children = new ArrayList(); if (tag == null) { try { List baseTags = searchable.getTags(); children = baseTags; } catch (IOException e) { error(e); } } else { try { searchable.fillTag(tag); } catch (IOException e) { error(e); } if (!tag.isLeaf()) { children = tag.getChildren(); } else { children = null; } } final List fchildren = children; inUi(new Runnable() { @Override public void run() { if (fchildren != null) { addTagBar(fchildren, tag); } if (inUi != null) { inUi.run(); } } }); } }).start(); } // item 0 = no selection, else = default selection public void search(final SupportType searchOn, final String keywords, final int page, final int item) { setWaitingScreen(true); updateSupportType(searchOn); updateSearchBy(false); updateKeywords(keywords); updatePages(page, maxPage); new Thread(new Runnable() { @Override public void run() { BasicSearchable search = BasicSearchable .getSearchable(searchOn); int maxPage = -1; try { maxPage = search.searchPages(keywords); } catch (IOException e) { error(e); } if (page <= 0) { updateBooks(new ArrayList()); updatePages(0, maxPage); } else { List results; try { results = search.search(keywords, page); } catch (IOException e) { error(e); results = new ArrayList(); } search(results, page, maxPage, item); // ! 1-based index ! if (item > 0 && item <= books.getBooksCount()) { // TODO: "click" on item ITEM } } setWaitingScreen(false); } }).start(); } // tag: null = base tags public void searchTag(final SupportType searchOn, final int page, final int item, final SearchableTag tag) { setWaitingScreen(true); updateSupportType(searchOn); updateSearchBy(true); updateTags(tag); updatePages(page, maxPage); new Thread(new Runnable() { @Override public void run() { BasicSearchable search = BasicSearchable .getSearchable(searchOn); if (tag != null) { try { search.fillTag(tag); } catch (IOException e) { error(e); } int maxPage = 0; try { maxPage = search.searchPages(tag); } catch (IOException e) { error(e); } updatePages(page, maxPage); if (page > 0) { List metas = new ArrayList(); if (tag.isLeaf()) { try { metas = search.search(tag, page); } catch (IOException e) { error(e); } } else { List subtags = tag.getChildren(); if (item > 0 && item <= subtags.size()) { SearchableTag subtag = subtags.get(item - 1); try { metas = search.search(subtag, page); maxPage = subtag.getPages(); } catch (IOException e) { error(e); } } } updatePages(page, maxPage); search(metas, page, maxPage, item); } } setWaitingScreen(false); } }).start(); } // item 0 = no selection, else = default selection public void search(final List results, final int page, final int maxPage, final int item) { updatePages(page, maxPage); if (page <= 0) { updateBooks(new ArrayList()); updatePages(0, maxPage); } else { List infos = new ArrayList(); for (MetaData meta : results) { infos.add(GuiReaderBookInfo.fromMeta(meta)); } updateBooks(infos); // ! 1-based index ! if (item > 0 && item <= books.getBooksCount()) { // TODO: "click" on item ITEM } } } /** * Process the given action in the main Swing UI thread. *

* The code will make sure the current thread is the main UI thread and, if * not, will switch to it before executing the runnable. *

* Synchronous operation. * * @param run * the action to run */ static void inUi(final Runnable run) { if (EventQueue.isDispatchThread()) { run.run(); } else { try { EventQueue.invokeAndWait(run); } catch (InterruptedException e) { error(e); } catch (InvocationTargetException e) { error(e); } } } static void error(Exception e) { Instance.getTraceHandler().error(e); } static void error(String e) { Instance.getTraceHandler().error(e); } private void setWaitingScreen(final boolean waiting) { inUi(new Runnable() { @Override public void run() { GuiReaderSearchFrame.this.setEnabled(!waiting); books.setEnabled(!waiting); submitKeywords.setEnabled(!waiting); } }); } }