GUI search: code cleanup + jDoc
authorNiki Roo <niki@nikiroo.be>
Fri, 19 Apr 2019 15:40:14 +0000 (17:40 +0200)
committerNiki Roo <niki@nikiroo.be>
Fri, 19 Apr 2019 15:40:14 +0000 (17:40 +0200)
src/be/nikiroo/fanfix/reader/ui/GuiReaderGroup.java
src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java
src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByPanel.java
src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByTagPanel.java
src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java

index a0023898bb5cadd31dc91be00e9e6c47727e2aea..7275498ff3d9c6079907886c07d092ae80adad1e 100644 (file)
@@ -362,7 +362,7 @@ public class GuiReaderGroup extends JPanel {
         * @param index
         *            the index of the book to select, can be outside the bounds
         *            (either all the items will be unselected or the first or last
-        *            book will then be selected, see <tt>forceRange>/tt>)
+        *            book will then be selected, see <tt>forceRange></tt>)
         * @param forceRange
         *            TRUE to constraint the index to the first/last element, FALSE
         *            to unselect when outside the range
index 94579fd4c7dbf58356b1554fcc964ef5a84b160b..51db24c12076fc9929c31028eb952ce8b12d74c3 100644 (file)
@@ -14,6 +14,7 @@ import javax.swing.JPanel;
 import javax.swing.JTextField;
 
 import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.reader.ui.GuiReaderSearchByPanel.Waitable;
 import be.nikiroo.fanfix.searchable.BasicSearchable;
 
 /**
@@ -35,7 +36,7 @@ public class GuiReaderSearchByNamePanel extends JPanel {
        private List<MetaData> stories = new ArrayList<MetaData>();
        private int storyItem;
 
-       public GuiReaderSearchByNamePanel(final Runnable fireEvent) {
+       public GuiReaderSearchByNamePanel(final Waitable waitable) {
                super(new BorderLayout());
 
                keywordsField = new JTextField();
@@ -44,11 +45,25 @@ public class GuiReaderSearchByNamePanel extends JPanel {
                submitKeywords = new JButton("Search");
                add(submitKeywords, BorderLayout.EAST);
 
+               // should be done out of UI
+               final Runnable go = new Runnable() {
+                       @Override
+                       public void run() {
+                               waitable.setWaiting(true);
+                               try {
+                                       search(keywordsField.getText(), 1, 0);
+                                       waitable.fireEvent();
+                               } finally {
+                                       waitable.setWaiting(false);
+                               }
+                       }
+               };
+
                keywordsField.addKeyListener(new KeyAdapter() {
                        @Override
                        public void keyReleased(KeyEvent e) {
                                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
-                                       search(keywordsField.getText(), 1, 0);
+                                       new Thread(go).start();
                                } else {
                                        super.keyReleased(e);
                                }
@@ -58,13 +73,7 @@ public class GuiReaderSearchByNamePanel extends JPanel {
                submitKeywords.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
-                               new Thread(new Runnable() {
-                                       @Override
-                                       public void run() {
-                                               search(keywordsField.getText(), 1, 0);
-                                               fireEvent.run();
-                                       }
-                               }).start();
+                               new Thread(go).start();
                        }
                });
 
@@ -88,28 +97,66 @@ public class GuiReaderSearchByNamePanel extends JPanel {
                updateKeywords("");
        }
 
+       /**
+        * The currently displayed page of result for the current search (see the
+        * <tt>page</tt> parameter of
+        * {@link GuiReaderSearchByNamePanel#search(String, int, int)}).
+        * 
+        * @return the currently displayed page of results
+        */
        public int getPage() {
                return page;
        }
 
+       /**
+        * The number of pages of result for the current search (see the
+        * <tt>page</tt> parameter of
+        * {@link GuiReaderSearchByPanel#search(String, int, int)}).
+        * <p>
+        * For an unknown number or when not applicable, -1 is returned.
+        * 
+        * @return the number of pages of results or -1
+        */
        public int getMaxPage() {
                return maxPage;
        }
 
+       /**
+        * Return the keywords used for the current search.
+        * 
+        * @return the keywords
+        */
        public String getCurrentKeywords() {
                return keywordsField.getText();
        }
 
+       /**
+        * The currently loaded stories (the result of the latest search).
+        * 
+        * @return the stories
+        */
        public List<MetaData> getStories() {
                return stories;
        }
 
-       // selected item or 0 if none ! one-based !
+       /**
+        * Return the currently selected story (the <tt>item</tt>) if it was
+        * specified in the latest, or 0 if not.
+        * <p>
+        * Note: this is thus a 1-based index, <b>not</b> a 0-based index.
+        * 
+        * @return the item
+        */
        public int getStoryItem() {
                return storyItem;
        }
 
-       // cannot be NULL
+       /**
+        * Update the keywords displayed on screen.
+        * 
+        * @param keywords
+        *            the keywords
+        */
        private void updateKeywords(final String keywords) {
                if (!keywords.equals(keywordsField.getText())) {
                        GuiReaderSearchFrame.inUi(new Runnable() {
@@ -121,8 +168,20 @@ public class GuiReaderSearchByNamePanel extends JPanel {
                }
        }
 
-       // item 0 = no selection, else = default selection
-       // throw if page > max
+       /**
+        * Search for the given terms on the currently selected searchable.
+        * <p>
+        * This operation can be long and should be run outside the UI thread.
+        * 
+        * @param keywords
+        *            the keywords to search for
+        * @param page
+        *            the page of results to load
+        * @param item
+        *            the item to select (or 0 for none by default)
+        * 
+        * @throw IndexOutOfBoundsException if the page is out of bounds
+        */
        public void search(String keywords, int page, int item) {
                List<MetaData> stories = new ArrayList<MetaData>();
                int storyItem = 0;
@@ -130,10 +189,12 @@ public class GuiReaderSearchByNamePanel extends JPanel {
                updateKeywords(keywords);
 
                int maxPage = -1;
-               try {
-                       maxPage = searchable.searchPages(keywords);
-               } catch (IOException e) {
-                       GuiReaderSearchFrame.error(e);
+               if (searchable != null) {
+                       try {
+                               maxPage = searchable.searchPages(keywords);
+                       } catch (IOException e) {
+                               GuiReaderSearchFrame.error(e);
+                       }
                }
 
                if (page > 0) {
@@ -142,11 +203,12 @@ public class GuiReaderSearchByNamePanel extends JPanel {
                                                + maxPage);
                        }
 
-                       try {
-                               stories = searchable.search(keywords, page);
-                       } catch (IOException e) {
-                               GuiReaderSearchFrame.error(e);
-                               stories = new ArrayList<MetaData>();
+                       if (searchable != null) {
+                               try {
+                                       stories = searchable.search(keywords, page);
+                               } catch (IOException e) {
+                                       GuiReaderSearchFrame.error(e);
+                               }
                        }
 
                        if (item > 0 && item <= stories.size()) {
index 4fcda5ccf762555c4f18d4b8e29d49b27db89a74..8f95d4cf34e17cbc9f6c5eb94789ff2398e82a8a 100644 (file)
@@ -1,9 +1,6 @@
 package be.nikiroo.fanfix.reader.ui;
 
 import java.awt.BorderLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.ArrayList;
 import java.util.List;
 
 import javax.swing.JPanel;
@@ -25,39 +22,52 @@ import be.nikiroo.fanfix.supported.SupportType;
 public class GuiReaderSearchByPanel extends JPanel {
        private static final long serialVersionUID = 1L;
 
-       private int actionEventId = ActionEvent.ACTION_FIRST;
-
        private Waitable waitable;
 
        private boolean searchByTags;
-
        private JTabbedPane searchTabs;
        private GuiReaderSearchByNamePanel byName;
        private GuiReaderSearchByTagPanel byTag;
 
-       private List<ActionListener> actions = new ArrayList<ActionListener>();
-
+       /**
+        * This interface represents an item that wan be put in "wait" mode. It is
+        * supposed to be used for long running operations during which we want to
+        * disable UI interactions.
+        * <p>
+        * It also allows reporting an event to the item.
+        * 
+        * @author niki
+        */
        public interface Waitable {
+               /**
+                * Set the item in wait mode, blocking it from accepting UI input.
+                * 
+                * @param waiting
+                *            TRUE for wait more, FALSE to restore normal mode
+                */
                public void setWaiting(boolean waiting);
+
+               /**
+                * Notify the {@link Waitable} that an event occured (i.e., new stories
+                * were found).
+                */
+               public void fireEvent();
        }
 
-       // will throw illegalArgEx if bad support type, NULL allowed
-       public GuiReaderSearchByPanel(final SupportType supportType,
-                       Waitable waitable) {
+       /**
+        * Create a new {@link GuiReaderSearchByPanel}.
+        * 
+        * @param waitable
+        *            the waitable we can wait on for long UI operations
+        */
+       public GuiReaderSearchByPanel(Waitable waitable) {
                setLayout(new BorderLayout());
 
                this.waitable = waitable;
                searchByTags = false;
 
-               Runnable fireEvent = new Runnable() {
-                       @Override
-                       public void run() {
-                               fireAction();
-                       }
-               };
-
-               byName = new GuiReaderSearchByNamePanel(fireEvent);
-               byTag = new GuiReaderSearchByTagPanel(fireEvent);
+               byName = new GuiReaderSearchByNamePanel(waitable);
+               byTag = new GuiReaderSearchByTagPanel(waitable);
 
                searchTabs = new JTabbedPane();
                searchTabs.addTab("By name", byName);
@@ -71,20 +81,43 @@ public class GuiReaderSearchByPanel extends JPanel {
 
                add(searchTabs, BorderLayout.CENTER);
                updateSearchBy(searchByTags);
-               setSupportType(supportType);
        }
 
+       /**
+        * Set the new {@link SupportType}.
+        * <p>
+        * This operation can be long and should be run outside the UI thread.
+        * <p>
+        * Note that if a non-searchable {@link SupportType} is used, an
+        * {@link IllegalArgumentException} will be thrown.
+        * 
+        * @param supportType
+        *            the support mode, must be searchable or NULL
+        * 
+        * @throws IllegalArgumentException
+        *             if the {@link SupportType} is not NULL but not searchable
+        *             (see {@link BasicSearchable#getSearchable(SupportType)})
+        */
        public void setSupportType(SupportType supportType) {
                BasicSearchable searchable = BasicSearchable.getSearchable(supportType);
                if (searchable == null && supportType != null) {
-                       throw new java.lang.IllegalArgumentException(
-                                       "Unupported support type: " + supportType);
+                       throw new IllegalArgumentException("Unupported support type: "
+                                       + supportType);
                }
 
                byName.setSearchable(searchable);
                byTag.setSearchable(searchable);
        }
 
+       /**
+        * The currently displayed page of result for the current search (see the
+        * <tt>page</tt> parameter of
+        * {@link GuiReaderSearchByPanel#search(String, int, int)} or
+        * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)}
+        * ).
+        * 
+        * @return the currently displayed page of results
+        */
        public int getPage() {
                if (!searchByTags) {
                        return byName.getPage();
@@ -93,6 +126,17 @@ public class GuiReaderSearchByPanel extends JPanel {
                return byTag.getPage();
        }
 
+       /**
+        * The number of pages of result for the current search (see the
+        * <tt>page</tt> parameter of
+        * {@link GuiReaderSearchByPanel#search(String, int, int)} or
+        * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)}
+        * ).
+        * <p>
+        * For an unknown number or when not applicable, -1 is returned.
+        * 
+        * @return the number of pages of results or -1
+        */
        public int getMaxPage() {
                if (!searchByTags) {
                        return byName.getMaxPage();
@@ -101,7 +145,17 @@ public class GuiReaderSearchByPanel extends JPanel {
                return byTag.getMaxPage();
        }
 
-       // throw outOfBounds if needed
+       /**
+        * Set the page of results to display for the current search. This will
+        * cause {@link Waitable#fireEvent()} to be called if needed.
+        * <p>
+        * This operation can be long and should be run outside the UI thread.
+        * 
+        * @param page
+        *            the page of results to set
+        * 
+        * @throw IndexOutOfBoundsException if the page is out of bounds
+        */
        public void setPage(int page) {
                if (searchByTags) {
                        searchTag(byTag.getCurrentTag(), page, 0);
@@ -110,15 +164,11 @@ public class GuiReaderSearchByPanel extends JPanel {
                }
        }
 
-       // actions will be fired in UIthread
-       public void addActionListener(ActionListener action) {
-               actions.add(action);
-       }
-
-       public boolean removeActionListener(ActionListener action) {
-               return actions.remove(action);
-       }
-
+       /**
+        * The currently loaded stories (the result of the latest search).
+        * 
+        * @return the stories
+        */
        public List<MetaData> getStories() {
                if (!searchByTags) {
                        return byName.getStories();
@@ -127,7 +177,14 @@ public class GuiReaderSearchByPanel extends JPanel {
                return byTag.getStories();
        }
 
-       // selected item or 0 if none ! one-based !
+       /**
+        * Return the currently selected story (the <tt>item</tt>) if it was
+        * specified in the latest, or 0 if not.
+        * <p>
+        * Note: this is thus a 1-based index, <b>not</b> a 0-based index.
+        * 
+        * @return the item
+        */
        public int getStoryItem() {
                if (!searchByTags) {
                        return byName.getStoryItem();
@@ -136,29 +193,13 @@ public class GuiReaderSearchByPanel extends JPanel {
                return byTag.getStoryItem();
        }
 
-       private void fireAction() {
-               GuiReaderSearchFrame.inUi(new Runnable() {
-                       @Override
-                       public void run() {
-                               ActionEvent ae = new ActionEvent(GuiReaderSearchByPanel.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);
-                                       }
-                               }
-                       }
-               });
-       }
-
+       /**
+        * Update the kind of searches to make: search by keywords or search by tags
+        * (it will impact what the user can see and interact with on the UI).
+        * 
+        * @param byTag
+        *            TRUE for tag-based searches, FALSE for keywords-based searches
+        */
        private void updateSearchBy(final boolean byTag) {
                GuiReaderSearchFrame.inUi(new Runnable() {
                        @Override
@@ -172,41 +213,51 @@ public class GuiReaderSearchByPanel extends JPanel {
                });
        }
 
-       // slow, start in UI mode
+       /**
+        * Search for the given terms on the currently selected searchable. This
+        * will cause {@link Waitable#fireEvent()} to be called if needed.
+        * <p>
+        * This operation can be long and should be run outside the UI thread.
+        * 
+        * @param keywords
+        *            the keywords to search for
+        * @param page
+        *            the page of results to load
+        * @param item
+        *            the item to select (or 0 for none by default)
+        * 
+        * @throw IndexOutOfBoundsException if the page is out of bounds
+        */
        public void search(final String keywords, final int page, final int item) {
-               waitable.setWaiting(true);
                updateSearchBy(false);
-               new Thread(new Runnable() {
-                       @Override
-                       public void run() {
-                               try {
-                                       byName.search(keywords, page, item);
-                                       fireAction();
-                               } finally {
-                                       waitable.setWaiting(false);
-                               }
-                       }
-               }).start();
+               byName.search(keywords, page, item);
+               waitable.fireEvent();
        }
 
-       // slow, start in UI mode
-       // tag: null = base tags
+       /**
+        * Search for the given tag on the currently selected searchable. This will
+        * cause {@link Waitable#fireEvent()} to be called if needed.
+        * <p>
+        * If the tag contains children tags, those will be displayed so you can
+        * select them; if the tag is a leaf tag, the linked stories will be
+        * displayed.
+        * <p>
+        * This operation can be long and should be run outside the UI thread.
+        * 
+        * @param tag
+        *            the tag to search for, or NULL for base tags
+        * @param page
+        *            the page of results to load
+        * @param item
+        *            the item to select (or 0 for none by default)
+        * 
+        * @throw IndexOutOfBoundsException if the page is out of bounds
+        */
        public void searchTag(final SearchableTag tag, final int page,
                        final int item) {
-               waitable.setWaiting(true);
                updateSearchBy(true);
-               new Thread(new Runnable() {
-                       @Override
-                       public void run() {
-                               try {
-
-                                       byTag.searchTag(tag, page, item);
-                                       fireAction();
-                               } finally {
-                                       waitable.setWaiting(false);
-                               }
-                       }
-               }).start();
+               byTag.searchTag(tag, page, item);
+               waitable.fireEvent();
        }
 
        /**
index aa00b8bd5eebed8588cf8b8fa596d3411b4575f8..a654d167b35bb7fc201e68cdb46c5897c4420501 100644 (file)
@@ -16,8 +16,10 @@ import javax.swing.JPanel;
 import javax.swing.ListCellRenderer;
 
 import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.reader.ui.GuiReaderSearchByPanel.Waitable;
 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
@@ -31,7 +33,7 @@ public class GuiReaderSearchByTagPanel extends JPanel {
        private static final long serialVersionUID = 1L;
 
        private BasicSearchable searchable;
-       private Runnable fireEvent;
+       private Waitable waitable;
 
        private SearchableTag currentTag;
        private JPanel tagBars;
@@ -42,10 +44,10 @@ public class GuiReaderSearchByTagPanel extends JPanel {
        private List<MetaData> stories = new ArrayList<MetaData>();
        private int storyItem;
 
-       public GuiReaderSearchByTagPanel(Runnable fireEvent) {
+       public GuiReaderSearchByTagPanel(Waitable waitable) {
                setLayout(new BorderLayout());
 
-               this.fireEvent = fireEvent;
+               this.waitable = waitable;
                combos = new ArrayList<JComboBox>();
                page = 1;
                maxPage = -1;
@@ -58,6 +60,8 @@ public class GuiReaderSearchByTagPanel extends JPanel {
        /**
         * The {@link BasicSearchable} object use for the searches themselves.
         * <p>
+        * This operation can be long and should be run outside the UI thread.
+        * <p>
         * Can be NULL, but no searches will work.
         * 
         * @param searchable
@@ -72,29 +76,70 @@ public class GuiReaderSearchByTagPanel extends JPanel {
                updateTags(null);
        }
 
+       /**
+        * The currently displayed page of result for the current search (see the
+        * <tt>page</tt> parameter of
+        * {@link GuiReaderSearchByTagPanel#searchTag(SupportType, int, int, SearchableTag)}
+        * ).
+        * 
+        * @return the currently displayed page of results
+        */
        public int getPage() {
                return page;
        }
 
+       /**
+        * The number of pages of result for the current search (see the
+        * <tt>page</tt> parameter of
+        * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)}
+        * ).
+        * <p>
+        * For an unknown number or when not applicable, -1 is returned.
+        * 
+        * @return the number of pages of results or -1
+        */
        public int getMaxPage() {
                return maxPage;
        }
 
+       /**
+        * Return the tag used for the current search.
+        * 
+        * @return the tag (which can be NULL, for "base tags")
+        */
        public SearchableTag getCurrentTag() {
                return currentTag;
        }
 
+       /**
+        * The currently loaded stories (the result of the latest search).
+        * 
+        * @return the stories
+        */
        public List<MetaData> getStories() {
                return stories;
        }
 
-       // selected item or 0 if none ! one-based !
+       /**
+        * Return the currently selected story (the <tt>item</tt>) if it was
+        * specified in the latest, or 0 if not.
+        * <p>
+        * Note: this is thus a 1-based index, <b>not</b> a 0-based index.
+        * 
+        * @return the item
+        */
        public int getStoryItem() {
                return storyItem;
        }
 
-       // update and reset the tagsbar
-       // can be NULL, for base tags
+       /**
+        * Update the tags displayed on screen and reset the tags bar.
+        * <p>
+        * This operation can be long and should be run outside the UI thread.
+        * 
+        * @param tag
+        *            the tag to use, or NULL for base tags
+        */
        private void updateTags(final SearchableTag tag) {
                final List<SearchableTag> parents = new ArrayList<SearchableTag>();
                SearchableTag parent = (tag == null) ? null : tag;
@@ -142,7 +187,16 @@ public class GuiReaderSearchByTagPanel extends JPanel {
                });
        }
 
-       // must be quick and no thread change
+       /**
+        * Add a tags bar (do not remove possible previous ones).
+        * <p>
+        * Will always add an "empty" (NULL) tag as first option.
+        * 
+        * @param tags
+        *            the tags to display
+        * @param selected
+        *            the selected tag if any, or NULL for none
+        */
        private void addTagBar(List<SearchableTag> tags,
                        final SearchableTag selected) {
                tags.add(0, null);
@@ -187,6 +241,22 @@ public class GuiReaderSearchByTagPanel extends JPanel {
                tagBars.add(combo);
        }
 
+       /**
+        * The action to do on {@link JComboBox} selection.
+        * <p>
+        * The content of the action is:
+        * <ul>
+        * <li>Remove all tags bar below this one</li>
+        * <li>Load the subtags if any in anew tags bar</li>
+        * <li>Load the related stories if the tag was a leaf tag and notify the
+        * {@link Waitable} (via {@link Waitable#fireEvent()})</li>
+        * </ul>
+        * 
+        * @param comboIndex
+        *            the index of the related {@link JComboBox}
+        * 
+        * @return the action
+        */
        private ActionListener createComboTagAction(final int comboIndex) {
                return new ActionListener() {
                        @Override
@@ -209,31 +279,36 @@ public class GuiReaderSearchByTagPanel extends JPanel {
                                new Thread(new Runnable() {
                                        @Override
                                        public void run() {
-                                               final List<SearchableTag> children = getChildrenForTag(tag);
-                                               if (children != null) {
-                                                       GuiReaderSearchFrame.inUi(new Runnable() {
-                                                               @Override
-                                                               public void run() {
-                                                                       addTagBar(children, tag);
+                                               waitable.setWaiting(true);
+                                               try {
+                                                       final List<SearchableTag> children = getChildrenForTag(tag);
+                                                       if (children != null) {
+                                                               GuiReaderSearchFrame.inUi(new Runnable() {
+                                                                       @Override
+                                                                       public void run() {
+                                                                               addTagBar(children, tag);
+                                                                       }
+                                                               });
+                                                       }
+
+                                                       if (tag != null && tag.isLeaf()) {
+                                                               storyItem = 0;
+                                                               try {
+                                                                       searchable.fillTag(tag);
+                                                                       page = 1;
+                                                                       stories = searchable.search(tag, 1);
+                                                                       maxPage = searchable.searchPages(tag);
+                                                               } catch (IOException e) {
+                                                                       GuiReaderSearchFrame.error(e);
+                                                                       page = 0;
+                                                                       maxPage = -1;
+                                                                       stories = new ArrayList<MetaData>();
                                                                }
-                                                       });
-                                               }
 
-                                               if (tag != null && tag.isLeaf()) {
-                                                       storyItem = 0;
-                                                       try {
-                                                               searchable.fillTag(tag);
-                                                               page = 1;
-                                                               stories = searchable.search(tag, 1);
-                                                               maxPage = searchable.searchPages(tag);
-                                                       } catch (IOException e) {
-                                                               GuiReaderSearchFrame.error(e);
-                                                               page = 0;
-                                                               maxPage = -1;
-                                                               stories = new ArrayList<MetaData>();
+                                                               waitable.fireEvent();
                                                        }
-
-                                                       fireEvent.run();
+                                               } finally {
+                                                       waitable.setWaiting(false);
                                                }
                                        }
                                }).start();
@@ -241,8 +316,19 @@ public class GuiReaderSearchByTagPanel extends JPanel {
                };
        }
 
-       // sync, add children of tag, NULL = base tags
-       // return children of the tag or base tags or NULL
+       /**
+        * Get the children of the given tag (or the base tags if the given tag is
+        * NULL).
+        * <p>
+        * This action will "fill" ({@link BasicSearchable#fillTag(SearchableTag)})
+        * the given tag if needed first.
+        * <p>
+        * This operation can be long and should be run outside the UI thread.
+        * 
+        * @param tag
+        *            the tag to search into or NULL for the base tags
+        * @return the children
+        */
        private List<SearchableTag> getChildrenForTag(final SearchableTag tag) {
                List<SearchableTag> children = new ArrayList<SearchableTag>();
                if (tag == null) {
@@ -269,9 +355,24 @@ public class GuiReaderSearchByTagPanel extends JPanel {
                return children;
        }
 
-       // slow
-       // tag: null = base tags
-       // throw if page > max, but only if stories
+       /**
+        * Search for the given tag on the currently selected searchable.
+        * <p>
+        * If the tag contains children tags, those will be displayed so you can
+        * select them; if the tag is a leaf tag, the linked stories will be
+        * displayed.
+        * <p>
+        * This operation can be long and should be run outside the UI thread.
+        * 
+        * @param tag
+        *            the tag to search for, or NULL for base tags
+        * @param page
+        *            the page of results to load
+        * @param item
+        *            the item to select (or 0 for none by default)
+        * 
+        * @throw IndexOutOfBoundsException if the page is out of bounds
+        */
        public void searchTag(SearchableTag tag, int page, int item) {
                List<MetaData> stories = new ArrayList<MetaData>();
                int storyItem = 0;
index c67c735dc178ab864ee18ea6a123e0c27e1fef79..11d45e41fc6374ae2e46af973b9d1e9478bfa44b 100644 (file)
@@ -34,11 +34,11 @@ public class GuiReaderSearchFrame extends JFrame {
        private static final long serialVersionUID = 1L;
 
        private List<SupportType> supportTypes;
-       private SupportType supportType;
        private int page;
        private int maxPage;
 
        private JComboBox comboSupportTypes;
+       private ActionListener comboSupportTypesListener;
        private GuiReaderSearchByPanel searchPanel;
 
        private boolean seeWordcount;
@@ -53,59 +53,65 @@ public class GuiReaderSearchFrame extends JFrame {
                maxPage = -1;
 
                supportTypes = new ArrayList<SupportType>();
+               supportTypes.add(null);
                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() {
+
+               comboSupportTypesListener = new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
+                               final SupportType support = (SupportType) comboSupportTypes
+                                               .getSelectedItem();
                                setWaiting(true);
-                               updateSupportType(
-                                               (SupportType) comboSupportTypes.getSelectedItem(),
-                                               new Runnable() {
-                                                       @Override
-                                                       public void run() {
-                                                               setWaiting(false);
-                                                       }
-                                               });
+                               new Thread(new Runnable() {
+                                       @Override
+                                       public void run() {
+                                               try {
+                                                       updateSupportType(support);
+                                               } finally {
+                                                       setWaiting(false);
+                                               }
+                                       }
+                               }).start();
                        }
-               });
+               };
+               comboSupportTypes.addActionListener(comboSupportTypesListener);
+
                JPanel searchSites = new JPanel(new BorderLayout());
                searchSites.add(comboSupportTypes, BorderLayout.CENTER);
                searchSites.add(new JLabel(" " + "Website : "), BorderLayout.WEST);
 
-               searchPanel = new GuiReaderSearchByPanel(supportType,
+               searchPanel = new GuiReaderSearchByPanel(
                                new GuiReaderSearchByPanel.Waitable() {
                                        @Override
                                        public void setWaiting(boolean waiting) {
                                                GuiReaderSearchFrame.this.setWaiting(waiting);
                                        }
-                               });
 
-               searchPanel.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               updatePages(searchPanel.getPage(), searchPanel.getMaxPage());
-                               List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
-                               for (MetaData meta : searchPanel.getStories()) {
-                                       infos.add(GuiReaderBookInfo.fromMeta(meta));
-                               }
-
-                               updateBooks(infos);
-
-                               // ! 1-based index !
-                               int item = searchPanel.getStoryItem();
-                               if (item > 0 && item <= books.getBooksCount()) {
-                                       // TODO: "click" on item ITEM
-                               }
-                       }
-               });
+                                       @Override
+                                       public void fireEvent() {
+                                               updatePages(searchPanel.getPage(),
+                                                               searchPanel.getMaxPage());
+                                               List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
+                                               for (MetaData meta : searchPanel.getStories()) {
+                                                       infos.add(GuiReaderBookInfo.fromMeta(meta));
+                                               }
+
+                                               updateBooks(infos);
+
+                                               // ! 1-based index !
+                                               int item = searchPanel.getStoryItem();
+                                               if (item > 0 && item <= books.getBooksCount()) {
+                                                       books.setSelectedBook(item - 1, false);
+                                               }
+                                       }
+                               });
 
                JPanel top = new JPanel(new BorderLayout());
                top.add(searchSites, BorderLayout.NORTH);
@@ -135,21 +141,46 @@ public class GuiReaderSearchFrame extends JFrame {
                add(scroll, BorderLayout.CENTER);
        }
 
-       private void updateSupportType(final SupportType supportType,
-                       final Runnable inUi) {
-               this.supportType = supportType;
-               comboSupportTypes.setSelectedItem(supportType);
-               books.clear();
-
-               new Thread(new Runnable() {
+       /**
+        * Update the {@link SupportType} currently displayed to the user.
+        * <p>
+        * Will also cause a search for the new base tags of the given support if
+        * not NULL.
+        * <p>
+        * This operation can be long and should be run outside the UI thread.
+        * 
+        * @param supportType
+        *            the new {@link SupportType}
+        */
+       private void updateSupportType(final SupportType supportType) {
+               inUi(new Runnable() {
                        @Override
                        public void run() {
-                               searchPanel.setSupportType(supportType);
-                               inUi(inUi);
+                               books.clear();
+
+                               comboSupportTypes
+                                               .removeActionListener(comboSupportTypesListener);
+                               comboSupportTypes.setSelectedItem(supportType);
+                               comboSupportTypes.addActionListener(comboSupportTypesListener);
+
                        }
-               }).start();
+               });
+
+               searchPanel.setSupportType(supportType);
        }
 
+       /**
+        * Update the pages and the lined buttons currently displayed on screen.
+        * <p>
+        * Those are the same pages and maximum pages used by
+        * {@link GuiReaderSearchByPanel#search(String, int, int)} and
+        * {@link GuiReaderSearchByPanel#searchTag(SearchableTag, int, int)}.
+        * 
+        * @param page
+        *            the current page of results
+        * @param maxPage
+        *            the maximum number of pages of results
+        */
        private void updatePages(final int page, final int maxPage) {
                inUi(new Runnable() {
                        @Override
@@ -164,51 +195,79 @@ public class GuiReaderSearchFrame extends JFrame {
                });
        }
 
+       /**
+        * Update the currently displayed books.
+        * 
+        * @param infos
+        *            the new books
+        */
        private void updateBooks(final List<GuiReaderBookInfo> infos) {
-               setWaiting(true);
                inUi(new Runnable() {
                        @Override
                        public void run() {
                                books.refreshBooks(infos, seeWordcount);
-                               setWaiting(false);
                        }
                });
        }
 
-       // item 0 = no selection, else = default selection
+       /**
+        * Search for the given terms on the currently selected searchable. This
+        * will update the displayed books if needed.
+        * <p>
+        * This operation is asynchronous.
+        * 
+        * @param keywords
+        *            the keywords to search for
+        * @param page
+        *            the page of results to load
+        * @param item
+        *            the item to select (or 0 for none by default)
+        */
        public void search(final SupportType searchOn, final String keywords,
                        final int page, final int item) {
                setWaiting(true);
                new Thread(new Runnable() {
                        @Override
                        public void run() {
-                               searchPanel.setSupportType(searchOn);
-                               searchPanel.search(keywords, page, item);
-                               inUi(new Runnable() {
-                                       @Override
-                                       public void run() {
-                                               setWaiting(false);
-                                       }
-                               });
+                               try {
+                                       updateSupportType(searchOn);
+                                       searchPanel.search(keywords, page, item);
+                               } finally {
+                                       setWaiting(false);
+                               }
                        }
                }).start();
        }
 
-       // tag: null = base tags
+       /**
+        * Search for the given tag on the currently selected searchable. This will
+        * update the displayed books if needed.
+        * <p>
+        * If the tag contains children tags, those will be displayed so you can
+        * select them; if the tag is a leaf tag, the linked stories will be
+        * displayed.
+        * <p>
+        * This operation is asynchronous.
+        * 
+        * @param tag
+        *            the tag to search for, or NULL for base tags
+        * @param page
+        *            the page of results to load
+        * @param item
+        *            the item to select (or 0 for none by default)
+        */
        public void searchTag(final SupportType searchOn, final int page,
                        final int item, final SearchableTag tag) {
                setWaiting(true);
                new Thread(new Runnable() {
                        @Override
                        public void run() {
-                               searchPanel.setSupportType(searchOn);
-                               searchPanel.searchTag(tag, page, item);
-                               inUi(new Runnable() {
-                                       @Override
-                                       public void run() {
-                                               setWaiting(false);
-                                       }
-                               });
+                               try {
+                                       updateSupportType(searchOn);
+                                       searchPanel.searchTag(tag, page, item);
+                               } finally {
+                                       setWaiting(false);
+                               }
                        }
                }).start();
        }
@@ -238,10 +297,22 @@ public class GuiReaderSearchFrame extends JFrame {
                }
        }
 
+       /**
+        * An error occurred, inform the user and/or log the error.
+        * 
+        * @param e
+        *            the error
+        */
        static void error(Exception e) {
                Instance.getTraceHandler().error(e);
        }
 
+       /**
+        * An error occurred, inform the user and/or log the error.
+        * 
+        * @param e
+        *            the error message
+        */
        static void error(String e) {
                Instance.getTraceHandler().error(e);
        }
@@ -264,6 +335,12 @@ public class GuiReaderSearchFrame extends JFrame {
                searchPanel.setEnabled(b);
        }
 
+       /**
+        * Set the item in wait mode, blocking it from accepting UI input.
+        * 
+        * @param waiting
+        *            TRUE for wait more, FALSE to restore normal mode
+        */
        private void setWaiting(final boolean waiting) {
                inUi(new Runnable() {
                        @Override