make it subtree
[fanfix.git] / reader / ui / GuiReaderSearchByNamePanel.java
diff --git a/reader/ui/GuiReaderSearchByNamePanel.java b/reader/ui/GuiReaderSearchByNamePanel.java
new file mode 100644 (file)
index 0000000..ebdb21a
--- /dev/null
@@ -0,0 +1,246 @@
+package be.nikiroo.fanfix.reader.ui;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JButton;
+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;
+
+/**
+ * This panel represents a search panel that works for keywords and tags based
+ * searches.
+ * 
+ * @author niki
+ */
+public class GuiReaderSearchByNamePanel extends JPanel {
+       private static final long serialVersionUID = 1L;
+
+       private BasicSearchable searchable;
+
+       private JTextField keywordsField;
+       private JButton submitKeywords;
+
+       private int page;
+       private int maxPage;
+       private List<MetaData> stories = new ArrayList<MetaData>();
+       private int storyItem;
+
+       public GuiReaderSearchByNamePanel(final Waitable waitable) {
+               super(new BorderLayout());
+
+               keywordsField = new JTextField();
+               add(keywordsField, BorderLayout.CENTER);
+
+               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) {
+                                       new Thread(go).start();
+                               } else {
+                                       super.keyReleased(e);
+                               }
+                       }
+               });
+
+               submitKeywords.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               new Thread(go).start();
+                       }
+               });
+
+               setSearchable(null);
+       }
+
+       /**
+        * The {@link BasicSearchable} object use for the searches themselves.
+        * <p>
+        * Can be NULL, but no searches will work.
+        * 
+        * @param searchable
+        *            the new searchable
+        */
+       public void setSearchable(BasicSearchable searchable) {
+               this.searchable = searchable;
+               page = 0;
+               maxPage = -1;
+               storyItem = 0;
+               stories = new ArrayList<MetaData>();
+               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;
+       }
+
+       /**
+        * 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 the keywords displayed on screen.
+        * 
+        * @param keywords
+        *            the keywords
+        */
+       private void updateKeywords(final String keywords) {
+               if (!keywords.equals(keywordsField.getText())) {
+                       GuiReaderSearchFrame.inUi(new Runnable() {
+                               @Override
+                               public void run() {
+                                       keywordsField.setText(keywords);
+                               }
+                       });
+               }
+       }
+
+       /**
+        * 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;
+
+               updateKeywords(keywords);
+
+               int maxPage = -1;
+               if (searchable != null) {
+                       try {
+                               maxPage = searchable.searchPages(keywords);
+                       } catch (IOException e) {
+                               GuiReaderSearchFrame.error(e);
+                       }
+               }
+
+               if (page > 0) {
+                       if (maxPage >= 0 && (page <= 0 || page > maxPage)) {
+                               throw new IndexOutOfBoundsException("Page " + page + " out of "
+                                               + maxPage);
+                       }
+
+                       if (searchable != null) {
+                               try {
+                                       stories = searchable.search(keywords, page);
+                               } catch (IOException e) {
+                                       GuiReaderSearchFrame.error(e);
+                               }
+                       }
+
+                       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.page = page;
+               this.maxPage = maxPage;
+               this.stories = stories;
+               this.storyItem = storyItem;
+       }
+
+       /**
+        * Enables or disables this component, depending on the value of the
+        * parameter <code>b</code>. An enabled component can respond to user input
+        * and generate events. Components are enabled initially by default.
+        * <p>
+        * Disabling this component will also affect its children.
+        * 
+        * @param b
+        *            If <code>true</code>, this component is enabled; otherwise
+        *            this component is disabled
+        */
+       @Override
+       public void setEnabled(boolean b) {
+               super.setEnabled(b);
+               keywordsField.setEnabled(b);
+               submitKeywords.setEnabled(b);
+       }
+}