Merge branch 'subtree'
[fanfix.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderSearchByNamePanel.java
1 package be.nikiroo.fanfix.reader.ui;
2
3 import java.awt.BorderLayout;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.ActionListener;
6 import java.awt.event.KeyAdapter;
7 import java.awt.event.KeyEvent;
8 import java.io.IOException;
9 import java.util.ArrayList;
10 import java.util.List;
11
12 import javax.swing.JButton;
13 import javax.swing.JPanel;
14 import javax.swing.JTextField;
15
16 import be.nikiroo.fanfix.data.MetaData;
17 import be.nikiroo.fanfix.reader.ui.GuiReaderSearchByPanel.Waitable;
18 import be.nikiroo.fanfix.searchable.BasicSearchable;
19
20 /**
21 * This panel represents a search panel that works for keywords and tags based
22 * searches.
23 *
24 * @author niki
25 */
26 public class GuiReaderSearchByNamePanel extends JPanel {
27 private static final long serialVersionUID = 1L;
28
29 private BasicSearchable searchable;
30
31 private JTextField keywordsField;
32 private JButton submitKeywords;
33
34 private int page;
35 private int maxPage;
36 private List<MetaData> stories = new ArrayList<MetaData>();
37 private int storyItem;
38
39 public GuiReaderSearchByNamePanel(final Waitable waitable) {
40 super(new BorderLayout());
41
42 keywordsField = new JTextField();
43 add(keywordsField, BorderLayout.CENTER);
44
45 submitKeywords = new JButton("Search");
46 add(submitKeywords, BorderLayout.EAST);
47
48 // should be done out of UI
49 final Runnable go = new Runnable() {
50 @Override
51 public void run() {
52 waitable.setWaiting(true);
53 try {
54 search(keywordsField.getText(), 1, 0);
55 waitable.fireEvent();
56 } finally {
57 waitable.setWaiting(false);
58 }
59 }
60 };
61
62 keywordsField.addKeyListener(new KeyAdapter() {
63 @Override
64 public void keyReleased(KeyEvent e) {
65 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
66 new Thread(go).start();
67 } else {
68 super.keyReleased(e);
69 }
70 }
71 });
72
73 submitKeywords.addActionListener(new ActionListener() {
74 @Override
75 public void actionPerformed(ActionEvent e) {
76 new Thread(go).start();
77 }
78 });
79
80 setSearchable(null);
81 }
82
83 /**
84 * The {@link BasicSearchable} object use for the searches themselves.
85 * <p>
86 * Can be NULL, but no searches will work.
87 *
88 * @param searchable
89 * the new searchable
90 */
91 public void setSearchable(BasicSearchable searchable) {
92 this.searchable = searchable;
93 page = 0;
94 maxPage = -1;
95 storyItem = 0;
96 stories = new ArrayList<MetaData>();
97 updateKeywords("");
98 }
99
100 /**
101 * The currently displayed page of result for the current search (see the
102 * <tt>page</tt> parameter of
103 * {@link GuiReaderSearchByNamePanel#search(String, int, int)}).
104 *
105 * @return the currently displayed page of results
106 */
107 public int getPage() {
108 return page;
109 }
110
111 /**
112 * The number of pages of result for the current search (see the
113 * <tt>page</tt> parameter of
114 * {@link GuiReaderSearchByPanel#search(String, int, int)}).
115 * <p>
116 * For an unknown number or when not applicable, -1 is returned.
117 *
118 * @return the number of pages of results or -1
119 */
120 public int getMaxPage() {
121 return maxPage;
122 }
123
124 /**
125 * Return the keywords used for the current search.
126 *
127 * @return the keywords
128 */
129 public String getCurrentKeywords() {
130 return keywordsField.getText();
131 }
132
133 /**
134 * The currently loaded stories (the result of the latest search).
135 *
136 * @return the stories
137 */
138 public List<MetaData> getStories() {
139 return stories;
140 }
141
142 /**
143 * Return the currently selected story (the <tt>item</tt>) if it was
144 * specified in the latest, or 0 if not.
145 * <p>
146 * Note: this is thus a 1-based index, <b>not</b> a 0-based index.
147 *
148 * @return the item
149 */
150 public int getStoryItem() {
151 return storyItem;
152 }
153
154 /**
155 * Update the keywords displayed on screen.
156 *
157 * @param keywords
158 * the keywords
159 */
160 private void updateKeywords(final String keywords) {
161 if (!keywords.equals(keywordsField.getText())) {
162 GuiReaderSearchFrame.inUi(new Runnable() {
163 @Override
164 public void run() {
165 keywordsField.setText(keywords);
166 }
167 });
168 }
169 }
170
171 /**
172 * Search for the given terms on the currently selected searchable.
173 * <p>
174 * This operation can be long and should be run outside the UI thread.
175 *
176 * @param keywords
177 * the keywords to search for
178 * @param page
179 * the page of results to load
180 * @param item
181 * the item to select (or 0 for none by default)
182 *
183 * @throw IndexOutOfBoundsException if the page is out of bounds
184 */
185 public void search(String keywords, int page, int item) {
186 List<MetaData> stories = new ArrayList<MetaData>();
187 int storyItem = 0;
188
189 updateKeywords(keywords);
190
191 int maxPage = -1;
192 if (searchable != null) {
193 try {
194 maxPage = searchable.searchPages(keywords);
195 } catch (IOException e) {
196 GuiReaderSearchFrame.error(e);
197 }
198 }
199
200 if (page > 0) {
201 if (maxPage >= 0 && (page <= 0 || page > maxPage)) {
202 throw new IndexOutOfBoundsException("Page " + page + " out of "
203 + maxPage);
204 }
205
206 if (searchable != null) {
207 try {
208 stories = searchable.search(keywords, page);
209 } catch (IOException e) {
210 GuiReaderSearchFrame.error(e);
211 }
212 }
213
214 if (item > 0 && item <= stories.size()) {
215 storyItem = item;
216 } else if (item > 0) {
217 GuiReaderSearchFrame.error(String.format(
218 "Story item does not exist: Search [%s], item %d",
219 keywords, item));
220 }
221 }
222
223 this.page = page;
224 this.maxPage = maxPage;
225 this.stories = stories;
226 this.storyItem = storyItem;
227 }
228
229 /**
230 * Enables or disables this component, depending on the value of the
231 * parameter <code>b</code>. An enabled component can respond to user input
232 * and generate events. Components are enabled initially by default.
233 * <p>
234 * Disabling this component will also affect its children.
235 *
236 * @param b
237 * If <code>true</code>, this component is enabled; otherwise
238 * this component is disabled
239 */
240 @Override
241 public void setEnabled(boolean b) {
242 super.setEnabled(b);
243 keywordsField.setEnabled(b);
244 submitKeywords.setEnabled(b);
245 }
246 }