test: improve flag files
[nikiroo-utils.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderSearchFrame.java
1 package be.nikiroo.fanfix.reader.ui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.EventQueue;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.lang.reflect.InvocationTargetException;
9 import java.util.ArrayList;
10 import java.util.List;
11
12 import javax.swing.JComboBox;
13 import javax.swing.JFrame;
14 import javax.swing.JLabel;
15 import javax.swing.JPanel;
16 import javax.swing.JScrollPane;
17
18 import be.nikiroo.fanfix.Instance;
19 import be.nikiroo.fanfix.data.MetaData;
20 import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener;
21 import be.nikiroo.fanfix.searchable.BasicSearchable;
22 import be.nikiroo.fanfix.searchable.SearchableTag;
23 import be.nikiroo.fanfix.supported.SupportType;
24
25 /**
26 * This frame will allow you to search through the supported websites for new
27 * stories/comics.
28 *
29 * @author niki
30 */
31 // JCombobox<E> not 1.6 compatible
32 @SuppressWarnings({ "unchecked", "rawtypes" })
33 public class GuiReaderSearchFrame extends JFrame {
34 private static final long serialVersionUID = 1L;
35
36 private List<SupportType> supportTypes;
37
38 private JComboBox comboSupportTypes;
39 private ActionListener comboSupportTypesListener;
40 private GuiReaderSearchByPanel searchPanel;
41 private GuiReaderNavBar navbar;
42
43 private boolean seeWordcount;
44 private GuiReaderGroup books;
45
46 public GuiReaderSearchFrame(final GuiReader reader) {
47 super("Browse stories");
48 setLayout(new BorderLayout());
49 setSize(800, 600);
50
51 supportTypes = new ArrayList<SupportType>();
52 supportTypes.add(null);
53 for (SupportType type : SupportType.values()) {
54 if (BasicSearchable.getSearchable(type) != null) {
55 supportTypes.add(type);
56 }
57 }
58
59 comboSupportTypes = new JComboBox(
60 supportTypes.toArray(new SupportType[] {}));
61
62 comboSupportTypesListener = new ActionListener() {
63 @Override
64 public void actionPerformed(ActionEvent e) {
65 final SupportType support = (SupportType) comboSupportTypes
66 .getSelectedItem();
67 setWaiting(true);
68 new Thread(new Runnable() {
69 @Override
70 public void run() {
71 try {
72 updateSupportType(support);
73 } finally {
74 setWaiting(false);
75 }
76 }
77 }).start();
78 }
79 };
80 comboSupportTypes.addActionListener(comboSupportTypesListener);
81
82 JPanel searchSites = new JPanel(new BorderLayout());
83 searchSites.add(comboSupportTypes, BorderLayout.CENTER);
84 searchSites.add(new JLabel(" " + "Website : "), BorderLayout.WEST);
85
86 searchPanel = new GuiReaderSearchByPanel(
87 new GuiReaderSearchByPanel.Waitable() {
88 @Override
89 public void setWaiting(boolean waiting) {
90 GuiReaderSearchFrame.this.setWaiting(waiting);
91 }
92
93 @Override
94 public void fireEvent() {
95 updatePages(searchPanel.getPage(),
96 searchPanel.getMaxPage());
97 List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
98 for (MetaData meta : searchPanel.getStories()) {
99 infos.add(GuiReaderBookInfo.fromMeta(meta));
100 }
101
102 int page = searchPanel.getPage();
103 if (page <= 0) {
104 navbar.setMin(1);
105 navbar.setMax(1);
106 } else {
107 int max = searchPanel.getMaxPage();
108 navbar.setMin(1);
109 navbar.setMax(max);
110 navbar.setIndex(page);
111 }
112 updateBooks(infos);
113
114 // ! 1-based index !
115 int item = searchPanel.getStoryItem();
116 if (item > 0 && item <= books.getBooksCount()) {
117 books.setSelectedBook(item - 1, false);
118 }
119 }
120 });
121
122 JPanel top = new JPanel(new BorderLayout());
123 top.add(searchSites, BorderLayout.NORTH);
124 top.add(searchPanel, BorderLayout.CENTER);
125
126 add(top, BorderLayout.NORTH);
127
128 books = new GuiReaderGroup(reader, null, null);
129 books.setActionListener(new BookActionListener() {
130 @Override
131 public void select(GuiReaderBook book) {
132 }
133
134 @Override
135 public void popupRequested(GuiReaderBook book, Component target,
136 int x, int y) {
137 }
138
139 @Override
140 public void action(GuiReaderBook book) {
141 new GuiReaderSearchAction(reader.getLibrary(), book.getInfo())
142 .setVisible(true);
143 }
144 });
145 JScrollPane scroll = new JScrollPane(books);
146 scroll.getVerticalScrollBar().setUnitIncrement(16);
147 add(scroll, BorderLayout.CENTER);
148
149 navbar = new GuiReaderNavBar(-1, -1) {
150 private static final long serialVersionUID = 1L;
151
152 @Override
153 protected String computeLabel(int index, int min, int max) {
154 if (index <= 0) {
155 return "";
156 }
157 return super.computeLabel(index, min, max);
158 }
159 };
160
161 navbar.addActionListener(new ActionListener() {
162 @Override
163 public void actionPerformed(ActionEvent e) {
164 searchPanel.setPage(navbar.getIndex());
165 }
166 });
167
168 add(navbar, BorderLayout.SOUTH);
169 }
170
171 /**
172 * Update the {@link SupportType} currently displayed to the user.
173 * <p>
174 * Will also cause a search for the new base tags of the given support if
175 * not NULL.
176 * <p>
177 * This operation can be long and should be run outside the UI thread.
178 *
179 * @param supportType
180 * the new {@link SupportType}
181 */
182 private void updateSupportType(final SupportType supportType) {
183 inUi(new Runnable() {
184 @Override
185 public void run() {
186 books.clear();
187
188 comboSupportTypes
189 .removeActionListener(comboSupportTypesListener);
190 comboSupportTypes.setSelectedItem(supportType);
191 comboSupportTypes.addActionListener(comboSupportTypesListener);
192 }
193 });
194
195 searchPanel.setSupportType(supportType);
196 }
197
198 /**
199 * Update the pages and the lined buttons currently displayed on screen.
200 * <p>
201 * Those are the same pages and maximum pages used by
202 * {@link GuiReaderSearchByPanel#search(String, int, int)} and
203 * {@link GuiReaderSearchByPanel#searchTag(SearchableTag, int, int)}.
204 *
205 * @param page
206 * the current page of results
207 * @param maxPage
208 * the maximum number of pages of results
209 */
210 private void updatePages(final int page, final int maxPage) {
211 inUi(new Runnable() {
212 @Override
213 public void run() {
214 if (maxPage >= 1) {
215 navbar.setMin(1);
216 navbar.setMax(maxPage);
217 navbar.setIndex(page);
218 } else {
219 navbar.setMin(-1);
220 navbar.setMax(-1);
221 }
222 }
223 });
224 }
225
226 /**
227 * Update the currently displayed books.
228 *
229 * @param infos
230 * the new books
231 */
232 private void updateBooks(final List<GuiReaderBookInfo> infos) {
233 inUi(new Runnable() {
234 @Override
235 public void run() {
236 books.refreshBooks(infos, seeWordcount);
237 }
238 });
239 }
240
241 /**
242 * Search for the given terms on the currently selected searchable. This
243 * will update the displayed books if needed.
244 * <p>
245 * This operation is asynchronous.
246 *
247 * @param keywords
248 * the keywords to search for
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 public void search(final SupportType searchOn, final String keywords,
255 final int page, final int item) {
256 setWaiting(true);
257 new Thread(new Runnable() {
258 @Override
259 public void run() {
260 try {
261 updateSupportType(searchOn);
262 searchPanel.search(keywords, page, item);
263 } finally {
264 setWaiting(false);
265 }
266 }
267 }).start();
268 }
269
270 /**
271 * Search for the given tag on the currently selected searchable. This will
272 * update the displayed books if needed.
273 * <p>
274 * If the tag contains children tags, those will be displayed so you can
275 * select them; if the tag is a leaf tag, the linked stories will be
276 * displayed.
277 * <p>
278 * This operation is asynchronous.
279 *
280 * @param tag
281 * the tag to search for, or NULL for base tags
282 * @param page
283 * the page of results to load
284 * @param item
285 * the item to select (or 0 for none by default)
286 */
287 public void searchTag(final SupportType searchOn, final int page,
288 final int item, final SearchableTag tag) {
289 setWaiting(true);
290 new Thread(new Runnable() {
291 @Override
292 public void run() {
293 try {
294 updateSupportType(searchOn);
295 searchPanel.searchTag(tag, page, item);
296 } finally {
297 setWaiting(false);
298 }
299 }
300 }).start();
301 }
302
303 /**
304 * Process the given action in the main Swing UI thread.
305 * <p>
306 * The code will make sure the current thread is the main UI thread and, if
307 * not, will switch to it before executing the runnable.
308 * <p>
309 * Synchronous operation.
310 *
311 * @param run
312 * the action to run
313 */
314 static void inUi(final Runnable run) {
315 if (EventQueue.isDispatchThread()) {
316 run.run();
317 } else {
318 try {
319 EventQueue.invokeAndWait(run);
320 } catch (InterruptedException e) {
321 error(e);
322 } catch (InvocationTargetException e) {
323 error(e);
324 }
325 }
326 }
327
328 /**
329 * An error occurred, inform the user and/or log the error.
330 *
331 * @param e
332 * the error
333 */
334 static void error(Exception e) {
335 Instance.getTraceHandler().error(e);
336 }
337
338 /**
339 * An error occurred, inform the user and/or log the error.
340 *
341 * @param e
342 * the error message
343 */
344 static void error(String e) {
345 Instance.getTraceHandler().error(e);
346 }
347
348 /**
349 * Enables or disables this component, depending on the value of the
350 * parameter <code>b</code>. An enabled component can respond to user input
351 * and generate events. Components are enabled initially by default.
352 * <p>
353 * Disabling this component will also affect its children.
354 *
355 * @param b
356 * If <code>true</code>, this component is enabled; otherwise
357 * this component is disabled
358 */
359 @Override
360 public void setEnabled(boolean b) {
361 super.setEnabled(b);
362 books.setEnabled(b);
363 searchPanel.setEnabled(b);
364 }
365
366 /**
367 * Set the item in wait mode, blocking it from accepting UI input.
368 *
369 * @param waiting
370 * TRUE for wait more, FALSE to restore normal mode
371 */
372 private void setWaiting(final boolean waiting) {
373 inUi(new Runnable() {
374 @Override
375 public void run() {
376 GuiReaderSearchFrame.this.setEnabled(!waiting);
377 }
378 });
379 }
380 }