Merge branch 'subtree'
[nikiroo-utils.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderSearchByPanel.java
1 package be.nikiroo.fanfix.reader.ui;
2
3 import java.awt.BorderLayout;
4 import java.util.List;
5
6 import javax.swing.JPanel;
7 import javax.swing.JTabbedPane;
8 import javax.swing.event.ChangeEvent;
9 import javax.swing.event.ChangeListener;
10
11 import be.nikiroo.fanfix.data.MetaData;
12 import be.nikiroo.fanfix.searchable.BasicSearchable;
13 import be.nikiroo.fanfix.searchable.SearchableTag;
14 import be.nikiroo.fanfix.supported.SupportType;
15
16 /**
17 * This panel represents a search panel that works for keywords and tags based
18 * searches.
19 *
20 * @author niki
21 */
22 public class GuiReaderSearchByPanel extends JPanel {
23 private static final long serialVersionUID = 1L;
24
25 private Waitable waitable;
26
27 private boolean searchByTags;
28 private JTabbedPane searchTabs;
29 private GuiReaderSearchByNamePanel byName;
30 private GuiReaderSearchByTagPanel byTag;
31
32 /**
33 * This interface represents an item that wan be put in "wait" mode. It is
34 * supposed to be used for long running operations during which we want to
35 * disable UI interactions.
36 * <p>
37 * It also allows reporting an event to the item.
38 *
39 * @author niki
40 */
41 public interface Waitable {
42 /**
43 * Set the item in wait mode, blocking it from accepting UI input.
44 *
45 * @param waiting
46 * TRUE for wait more, FALSE to restore normal mode
47 */
48 public void setWaiting(boolean waiting);
49
50 /**
51 * Notify the {@link Waitable} that an event occured (i.e., new stories
52 * were found).
53 */
54 public void fireEvent();
55 }
56
57 /**
58 * Create a new {@link GuiReaderSearchByPanel}.
59 *
60 * @param waitable
61 * the waitable we can wait on for long UI operations
62 */
63 public GuiReaderSearchByPanel(Waitable waitable) {
64 setLayout(new BorderLayout());
65
66 this.waitable = waitable;
67 searchByTags = false;
68
69 byName = new GuiReaderSearchByNamePanel(waitable);
70 byTag = new GuiReaderSearchByTagPanel(waitable);
71
72 searchTabs = new JTabbedPane();
73 searchTabs.addTab("By name", byName);
74 searchTabs.addTab("By tags", byTag);
75 searchTabs.addChangeListener(new ChangeListener() {
76 @Override
77 public void stateChanged(ChangeEvent e) {
78 searchByTags = (searchTabs.getSelectedComponent() == byTag);
79 }
80 });
81
82 add(searchTabs, BorderLayout.CENTER);
83 updateSearchBy(searchByTags);
84 }
85
86 /**
87 * Set the new {@link SupportType}.
88 * <p>
89 * This operation can be long and should be run outside the UI thread.
90 * <p>
91 * Note that if a non-searchable {@link SupportType} is used, an
92 * {@link IllegalArgumentException} will be thrown.
93 *
94 * @param supportType
95 * the support mode, must be searchable or NULL
96 *
97 * @throws IllegalArgumentException
98 * if the {@link SupportType} is not NULL but not searchable
99 * (see {@link BasicSearchable#getSearchable(SupportType)})
100 */
101 public void setSupportType(SupportType supportType) {
102 BasicSearchable searchable = BasicSearchable.getSearchable(supportType);
103 if (searchable == null && supportType != null) {
104 throw new IllegalArgumentException("Unupported support type: "
105 + supportType);
106 }
107
108 byName.setSearchable(searchable);
109 byTag.setSearchable(searchable);
110 }
111
112 /**
113 * The currently displayed page of result for the current search (see the
114 * <tt>page</tt> parameter of
115 * {@link GuiReaderSearchByPanel#search(String, int, int)} or
116 * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)}
117 * ).
118 *
119 * @return the currently displayed page of results
120 */
121 public int getPage() {
122 if (!searchByTags) {
123 return byName.getPage();
124 }
125
126 return byTag.getPage();
127 }
128
129 /**
130 * The number of pages of result for the current search (see the
131 * <tt>page</tt> parameter of
132 * {@link GuiReaderSearchByPanel#search(String, int, int)} or
133 * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)}
134 * ).
135 * <p>
136 * For an unknown number or when not applicable, -1 is returned.
137 *
138 * @return the number of pages of results or -1
139 */
140 public int getMaxPage() {
141 if (!searchByTags) {
142 return byName.getMaxPage();
143 }
144
145 return byTag.getMaxPage();
146 }
147
148 /**
149 * Set the page of results to display for the current search. This will
150 * cause {@link Waitable#fireEvent()} to be called if needed.
151 * <p>
152 * This operation can be long and should be run outside the UI thread.
153 *
154 * @param page
155 * the page of results to set
156 *
157 * @throw IndexOutOfBoundsException if the page is out of bounds
158 */
159 public void setPage(int page) {
160 if (searchByTags) {
161 searchTag(byTag.getCurrentTag(), page, 0);
162 } else {
163 search(byName.getCurrentKeywords(), page, 0);
164 }
165 }
166
167 /**
168 * The currently loaded stories (the result of the latest search).
169 *
170 * @return the stories
171 */
172 public List<MetaData> getStories() {
173 if (!searchByTags) {
174 return byName.getStories();
175 }
176
177 return byTag.getStories();
178 }
179
180 /**
181 * Return the currently selected story (the <tt>item</tt>) if it was
182 * specified in the latest, or 0 if not.
183 * <p>
184 * Note: this is thus a 1-based index, <b>not</b> a 0-based index.
185 *
186 * @return the item
187 */
188 public int getStoryItem() {
189 if (!searchByTags) {
190 return byName.getStoryItem();
191 }
192
193 return byTag.getStoryItem();
194 }
195
196 /**
197 * Update the kind of searches to make: search by keywords or search by tags
198 * (it will impact what the user can see and interact with on the UI).
199 *
200 * @param byTag
201 * TRUE for tag-based searches, FALSE for keywords-based searches
202 */
203 private void updateSearchBy(final boolean byTag) {
204 GuiReaderSearchFrame.inUi(new Runnable() {
205 @Override
206 public void run() {
207 if (!byTag) {
208 searchTabs.setSelectedIndex(0);
209 } else {
210 searchTabs.setSelectedIndex(1);
211 }
212 }
213 });
214 }
215
216 /**
217 * Search for the given terms on the currently selected searchable. This
218 * will cause {@link Waitable#fireEvent()} to be called if needed.
219 * <p>
220 * This operation can be long and should be run outside the UI thread.
221 *
222 * @param keywords
223 * the keywords to search for
224 * @param page
225 * the page of results to load
226 * @param item
227 * the item to select (or 0 for none by default)
228 *
229 * @throw IndexOutOfBoundsException if the page is out of bounds
230 */
231 public void search(final String keywords, final int page, final int item) {
232 updateSearchBy(false);
233 byName.search(keywords, page, item);
234 waitable.fireEvent();
235 }
236
237 /**
238 * Search for the given tag on the currently selected searchable. This will
239 * cause {@link Waitable#fireEvent()} to be called if needed.
240 * <p>
241 * If the tag contains children tags, those will be displayed so you can
242 * select them; if the tag is a leaf tag, the linked stories will be
243 * displayed.
244 * <p>
245 * This operation can be long and should be run outside the UI thread.
246 *
247 * @param tag
248 * the tag to search for, or NULL for base tags
249 * @param page
250 * the page of results to load
251 * @param item
252 * the item to select (or 0 for none by default)
253 *
254 * @throw IndexOutOfBoundsException if the page is out of bounds
255 */
256 public void searchTag(final SearchableTag tag, final int page,
257 final int item) {
258 updateSearchBy(true);
259 byTag.searchTag(tag, page, item);
260 waitable.fireEvent();
261 }
262
263 /**
264 * Enables or disables this component, depending on the value of the
265 * parameter <code>b</code>. An enabled component can respond to user input
266 * and generate events. Components are enabled initially by default.
267 * <p>
268 * Disabling this component will also affect its children.
269 *
270 * @param b
271 * If <code>true</code>, this component is enabled; otherwise
272 * this component is disabled
273 */
274 @Override
275 public void setEnabled(boolean b) {
276 super.setEnabled(b);
277 searchTabs.setEnabled(b);
278 byName.setEnabled(b);
279 byTag.setEnabled(b);
280 }
281 }