From 741e846789c07ef6ddbfb0e9de1c7f4664d41ad5 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Thu, 18 Apr 2019 09:18:29 +0200 Subject: [PATCH] GUI search: code reorg step 0 --- .../nikiroo/fanfix/reader/ui/GuiReader.java | 6 +- .../reader/ui/GuiReaderSearchByNamePanel.java | 495 ++++++++++++++++++ ...rSearch.java => GuiReaderSearchFrame.java} | 20 +- 3 files changed, 510 insertions(+), 11 deletions(-) create mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java rename src/be/nikiroo/fanfix/reader/ui/{GuiReaderSearch.java => GuiReaderSearchFrame.java} (96%) diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReader.java b/src/be/nikiroo/fanfix/reader/ui/GuiReader.java index 0cace9c..c075790 100644 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReader.java +++ b/src/be/nikiroo/fanfix/reader/ui/GuiReader.java @@ -223,7 +223,7 @@ class GuiReader extends BasicReader { @Override public void search(boolean sync) throws IOException { - GuiReaderSearch search = new GuiReaderSearch(this); + GuiReaderSearchFrame search = new GuiReaderSearchFrame(this); if (sync) { sync(search); } else { @@ -234,7 +234,7 @@ class GuiReader extends BasicReader { @Override public void search(SupportType searchOn, String keywords, int page, int item, boolean sync) { - GuiReaderSearch search = new GuiReaderSearch(this); + GuiReaderSearchFrame search = new GuiReaderSearchFrame(this); search.search(searchOn, keywords, page, item); if (sync) { sync(search); @@ -247,7 +247,7 @@ class GuiReader extends BasicReader { public void searchTag(final SupportType searchOn, final int page, final int item, final boolean sync, final Integer... tags) { - final GuiReaderSearch search = new GuiReaderSearch(GuiReader.this); + final GuiReaderSearchFrame search = new GuiReaderSearchFrame(GuiReader.this); final BasicSearchable searchable = BasicSearchable .getSearchable(searchOn); diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java new file mode 100644 index 0000000..a648b72 --- /dev/null +++ b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java @@ -0,0 +1,495 @@ +package be.nikiroo.fanfix.reader.ui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.ListCellRenderer; + +import be.nikiroo.fanfix.data.MetaData; +import be.nikiroo.fanfix.searchable.BasicSearchable; +import be.nikiroo.fanfix.searchable.SearchableTag; +import be.nikiroo.fanfix.supported.SupportType; + +/** + * This panel represents a search panel that works for keywords and tags based + * searches. + * + * @author niki + */ +// JCombobox not 1.6 compatible +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class GuiReaderSearchByNamePanel extends JPanel { + private static final long serialVersionUID = 1L; + + private int actionEventId = ActionEvent.ACTION_FIRST; + + private SupportType supportType; + private BasicSearchable searchable; + private int page; + private boolean searchByTags; + + private String keywords; + private JTabbedPane searchTabs; + private JTextField keywordsField; + private JButton submitKeywords; + + private JPanel tagBars; + private List combos; + private JComboBox comboSupportTypes; + + private List actions = new ArrayList(); + private List stories = new ArrayList(); + private int storyItem; + + // will throw illegalArgEx if bad support type + public GuiReaderSearchByNamePanel(SupportType supportType) { + setLayout(new BorderLayout()); + + setSupportType(supportType); + page = 1; + searchByTags = false; + + searchTabs = new JTabbedPane(); + searchTabs.addTab("By name", createByNameSearchPanel()); + searchTabs.addTab("By tags", createByTagSearchPanel()); + + add(searchTabs, BorderLayout.CENTER); + } + + 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(keywordsField.getText(), 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; + } + + public SupportType getSupportType() { + return supportType; + } + + public void setSupportType(SupportType supportType) { + BasicSearchable searchable = BasicSearchable.getSearchable(supportType); + if (searchable == null) { + throw new java.lang.IllegalArgumentException( + "Unupported support type: " + supportType); + } + + this.supportType = supportType; + this.searchable = searchable; + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + // TODO: set against maxPage + // TODO: update last search? + this.page = page; + } + + // actions will be fired in UIthread + public void addActionListener(ActionListener action) { + actions.add(action); + } + + public boolean removeActionListener(ActionListener action) { + return actions.remove(action); + } + + public List getStories() { + return stories; + } + + // selected item or 0 if none ! one-based ! + public int getStoryItem() { + return storyItem; + } + + private void fireAction() { + GuiReaderSearchFrame.inUi(new Runnable() { + @Override + public void run() { + ActionEvent ae = new ActionEvent( + GuiReaderSearchByNamePanel.this, actionEventId, + "stories found"); + + actionEventId++; + if (actionEventId > ActionEvent.ACTION_LAST) { + actionEventId = ActionEvent.ACTION_FIRST; + } + + for (ActionListener action : actions) { + try { + action.actionPerformed(ae); + } catch (Exception e) { + GuiReaderSearchFrame.error(e); + } + } + } + }); + } + + private void updateSearchBy(final boolean byTag) { + if (byTag != this.searchByTags) { + GuiReaderSearchFrame.inUi(new Runnable() { + @Override + public void run() { + if (!byTag) { + searchTabs.setSelectedIndex(0); + } else { + searchTabs.setSelectedIndex(1); + } + } + }); + } + } + + // cannot be NULL + private void updateKeywords(final String keywords) { + if (!keywords.equals(this.keywords)) { + GuiReaderSearchFrame.inUi(new Runnable() { + @Override + public void run() { + GuiReaderSearchByNamePanel.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(); + } + + List rootTags = null; + SearchableTag selectedRootTag = null; + selectedRootTag = parents.isEmpty() ? null : parents + .get(parents.size() - 1); + + try { + rootTags = searchable.getTags(); + } catch (IOException e) { + GuiReaderSearchFrame.error(e); + } + + final List rootTagsF = rootTags; + final SearchableTag selectedRootTagF = selectedRootTag; + + GuiReaderSearchFrame.inUi(new Runnable() { + @Override + public void run() { + tagBars.invalidate(); + tagBars.removeAll(); + + addTagBar(rootTagsF, selectedRootTagF); + + 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(); + } + }); + } + + // must be quick and no thread change + 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() { + 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 + // return: maxpage + public int search(String keywords, int item) { + List stories = new ArrayList(); + int storyItem = 0; + + updateSearchBy(false); + updateKeywords(keywords); + + int maxPage = -1; + try { + maxPage = searchable.searchPages(keywords); + } catch (IOException e) { + GuiReaderSearchFrame.error(e); + } + + if (page > 0) { + try { + stories = searchable.search(keywords, page); + } catch (IOException e) { + GuiReaderSearchFrame.error(e); + stories = new ArrayList(); + } + + if (item > 0 && item <= stories.size()) { + storyItem = item; + } else if (item > 0) { + GuiReaderSearchFrame.error(String.format( + "Story item does not exist: Search [%s], item %d", + keywords, item)); + } + } + + this.stories = stories; + this.storyItem = storyItem; + fireAction(); + + return maxPage; + } + + // tag: null = base tags + // return: max pages + public int searchTag(SearchableTag tag, int item) { + List stories = new ArrayList(); + int storyItem = 0; + + updateSearchBy(true); + updateTags(tag); + + int maxPage = 1; + if (tag != null) { + try { + searchable.fillTag(tag); + + if (!tag.isLeaf()) { + List subtags = tag.getChildren(); + if (item > 0 && item <= subtags.size()) { + SearchableTag subtag = subtags.get(item - 1); + try { + tag = subtag; + searchable.fillTag(tag); + } catch (IOException e) { + GuiReaderSearchFrame.error(e); + } + } else if (item > 0) { + GuiReaderSearchFrame.error(String.format( + "Tag item does not exist: Tag [%s], item %d", + tag.getFqName(), item)); + } + } + + maxPage = searchable.searchPages(tag); + if (page > 0) { + if (tag.isLeaf()) { + try { + stories = searchable.search(tag, page); + if (item > 0 && item <= stories.size()) { + storyItem = item; + } else if (item > 0) { + GuiReaderSearchFrame + .error(String + .format("Story item does not exist: Tag [%s], item %d", + tag.getFqName(), item)); + } + } catch (IOException e) { + GuiReaderSearchFrame.error(e); + } + } + } + } catch (IOException e) { + GuiReaderSearchFrame.error(e); + maxPage = 0; + } + } + + this.stories = stories; + this.storyItem = storyItem; + fireAction(); + + return maxPage; + } + + /** + * Enables or disables this component, depending on the value of the + * parameter b. An enabled component can respond to user input + * and generate events. Components are enabled initially by default. + *

+ * Disabling this component will also affect its children. + * + * @param b + * If true, this component is enabled; otherwise + * this component is disabled + */ + @Override + public void setEnabled(final boolean waiting) { + GuiReaderSearchFrame.inUi(new Runnable() { + @Override + public void run() { + GuiReaderSearchByNamePanel.super.setEnabled(!waiting); + keywordsField.setEnabled(!waiting); + submitKeywords.setEnabled(!waiting); + // TODO + } + }); + } +} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearch.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java similarity index 96% rename from src/be/nikiroo/fanfix/reader/ui/GuiReaderSearch.java rename to src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java index 50d3d82..dcbf927 100644 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearch.java +++ b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java @@ -38,7 +38,7 @@ import be.nikiroo.fanfix.supported.SupportType; */ // JCombobox not 1.6 compatible @SuppressWarnings({ "unchecked", "rawtypes" }) -public class GuiReaderSearch extends JFrame { +public class GuiReaderSearchFrame extends JFrame { private static final long serialVersionUID = 1L; private List supportTypes; @@ -59,7 +59,7 @@ public class GuiReaderSearch extends JFrame { private boolean seeWordcount; private GuiReaderGroup books; - public GuiReaderSearch(final GuiReader reader) { + public GuiReaderSearchFrame(final GuiReader reader) { super("Browse stories"); setLayout(new BorderLayout()); setSize(800, 600); @@ -183,8 +183,8 @@ public class GuiReaderSearch extends JFrame { inUi(new Runnable() { @Override public void run() { - GuiReaderSearch.this.page = page; - GuiReaderSearch.this.maxPage = maxPage; + GuiReaderSearchFrame.this.page = page; + GuiReaderSearchFrame.this.maxPage = maxPage; // TODO: gui System.out.println("page: " + page); System.out.println("max page: " + maxPage); @@ -198,7 +198,7 @@ public class GuiReaderSearch extends JFrame { inUi(new Runnable() { @Override public void run() { - GuiReaderSearch.this.keywords = keywords; + GuiReaderSearchFrame.this.keywords = keywords; keywordsField.setText(keywords); } }); @@ -532,7 +532,7 @@ public class GuiReaderSearch extends JFrame { * @param run * the action to run */ - private void inUi(final Runnable run) { + static void inUi(final Runnable run) { if (EventQueue.isDispatchThread()) { run.run(); } else { @@ -546,15 +546,19 @@ public class GuiReaderSearch extends JFrame { } } - private void error(Exception 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() { - GuiReaderSearch.this.setEnabled(!waiting); + GuiReaderSearchFrame.this.setEnabled(!waiting); books.setEnabled(!waiting); submitKeywords.setEnabled(!waiting); } -- 2.27.0