Commit | Line | Data |
---|---|---|
4357eb54 NR |
1 | package be.nikiroo.fanfix.reader.ui; |
2 | ||
3 | import java.awt.BorderLayout; | |
d16065ec | 4 | import java.awt.Component; |
4357eb54 NR |
5 | import java.awt.EventQueue; |
6 | import java.awt.event.ActionEvent; | |
7 | import java.awt.event.ActionListener; | |
4357eb54 NR |
8 | import java.lang.reflect.InvocationTargetException; |
9 | import java.util.ArrayList; | |
10 | import java.util.List; | |
11 | ||
4357eb54 NR |
12 | import javax.swing.JComboBox; |
13 | import javax.swing.JFrame; | |
08e2185a | 14 | import javax.swing.JLabel; |
4357eb54 NR |
15 | import javax.swing.JPanel; |
16 | import javax.swing.JScrollPane; | |
4357eb54 NR |
17 | |
18 | import be.nikiroo.fanfix.Instance; | |
19 | import be.nikiroo.fanfix.data.MetaData; | |
d16065ec | 20 | import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener; |
4357eb54 NR |
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 | */ | |
6e950847 NR |
31 | // JCombobox<E> not 1.6 compatible |
32 | @SuppressWarnings({ "unchecked", "rawtypes" }) | |
741e8467 | 33 | public class GuiReaderSearchFrame extends JFrame { |
4357eb54 NR |
34 | private static final long serialVersionUID = 1L; |
35 | ||
36 | private List<SupportType> supportTypes; | |
4357eb54 | 37 | |
415c7454 | 38 | private JComboBox comboSupportTypes; |
dc3b0033 | 39 | private ActionListener comboSupportTypesListener; |
9c598207 | 40 | private GuiReaderSearchByPanel searchPanel; |
cf032e29 | 41 | private GuiReaderNavBar navbar; |
4357eb54 NR |
42 | |
43 | private boolean seeWordcount; | |
44 | private GuiReaderGroup books; | |
45 | ||
741e8467 | 46 | public GuiReaderSearchFrame(final GuiReader reader) { |
4357eb54 NR |
47 | super("Browse stories"); |
48 | setLayout(new BorderLayout()); | |
49 | setSize(800, 600); | |
50 | ||
4357eb54 | 51 | supportTypes = new ArrayList<SupportType>(); |
dc3b0033 | 52 | supportTypes.add(null); |
4357eb54 NR |
53 | for (SupportType type : SupportType.values()) { |
54 | if (BasicSearchable.getSearchable(type) != null) { | |
55 | supportTypes.add(type); | |
56 | } | |
57 | } | |
4357eb54 | 58 | |
415c7454 | 59 | comboSupportTypes = new JComboBox( |
4357eb54 | 60 | supportTypes.toArray(new SupportType[] {})); |
dc3b0033 NR |
61 | |
62 | comboSupportTypesListener = new ActionListener() { | |
4357eb54 NR |
63 | @Override |
64 | public void actionPerformed(ActionEvent e) { | |
dc3b0033 NR |
65 | final SupportType support = (SupportType) comboSupportTypes |
66 | .getSelectedItem(); | |
9c598207 | 67 | setWaiting(true); |
dc3b0033 NR |
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(); | |
4357eb54 | 78 | } |
dc3b0033 NR |
79 | }; |
80 | comboSupportTypes.addActionListener(comboSupportTypesListener); | |
81 | ||
08e2185a NR |
82 | JPanel searchSites = new JPanel(new BorderLayout()); |
83 | searchSites.add(comboSupportTypes, BorderLayout.CENTER); | |
84 | searchSites.add(new JLabel(" " + "Website : "), BorderLayout.WEST); | |
4357eb54 | 85 | |
dc3b0033 | 86 | searchPanel = new GuiReaderSearchByPanel( |
9c598207 | 87 | new GuiReaderSearchByPanel.Waitable() { |
7cc1e743 | 88 | @Override |
9c598207 NR |
89 | public void setWaiting(boolean waiting) { |
90 | GuiReaderSearchFrame.this.setWaiting(waiting); | |
7cc1e743 | 91 | } |
7cc1e743 | 92 | |
dc3b0033 NR |
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 | ||
cf032e29 NR |
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 | } | |
dc3b0033 NR |
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 | }); | |
4357eb54 | 121 | |
08e2185a NR |
122 | JPanel top = new JPanel(new BorderLayout()); |
123 | top.add(searchSites, BorderLayout.NORTH); | |
ce5a42e7 | 124 | top.add(searchPanel, BorderLayout.CENTER); |
4357eb54 NR |
125 | |
126 | add(top, BorderLayout.NORTH); | |
127 | ||
128 | books = new GuiReaderGroup(reader, null, null); | |
d16065ec NR |
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 | }); | |
4357eb54 NR |
145 | JScrollPane scroll = new JScrollPane(books); |
146 | scroll.getVerticalScrollBar().setUnitIncrement(16); | |
147 | add(scroll, BorderLayout.CENTER); | |
cf032e29 NR |
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); | |
4357eb54 NR |
169 | } |
170 | ||
dc3b0033 NR |
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() { | |
81acd363 NR |
184 | @Override |
185 | public void run() { | |
dc3b0033 NR |
186 | books.clear(); |
187 | ||
188 | comboSupportTypes | |
189 | .removeActionListener(comboSupportTypesListener); | |
190 | comboSupportTypes.setSelectedItem(supportType); | |
191 | comboSupportTypes.addActionListener(comboSupportTypesListener); | |
7cc1e743 | 192 | } |
dc3b0033 NR |
193 | }); |
194 | ||
195 | searchPanel.setSupportType(supportType); | |
7cc1e743 NR |
196 | } |
197 | ||
dc3b0033 NR |
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 | */ | |
9c598207 | 210 | private void updatePages(final int page, final int maxPage) { |
7cc1e743 NR |
211 | inUi(new Runnable() { |
212 | @Override | |
213 | public void run() { | |
cf032e29 NR |
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 | } | |
81acd363 NR |
222 | } |
223 | }); | |
224 | } | |
225 | ||
dc3b0033 NR |
226 | /** |
227 | * Update the currently displayed books. | |
228 | * | |
229 | * @param infos | |
230 | * the new books | |
231 | */ | |
81acd363 NR |
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 | ||
dc3b0033 NR |
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 | */ | |
4357eb54 NR |
254 | public void search(final SupportType searchOn, final String keywords, |
255 | final int page, final int item) { | |
9c598207 NR |
256 | setWaiting(true); |
257 | new Thread(new Runnable() { | |
7cc1e743 NR |
258 | @Override |
259 | public void run() { | |
dc3b0033 NR |
260 | try { |
261 | updateSupportType(searchOn); | |
262 | searchPanel.search(keywords, page, item); | |
263 | } finally { | |
264 | setWaiting(false); | |
265 | } | |
7cc1e743 | 266 | } |
9c598207 | 267 | }).start(); |
4357eb54 NR |
268 | } |
269 | ||
dc3b0033 NR |
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 | */ | |
c499d79f NR |
287 | public void searchTag(final SupportType searchOn, final int page, |
288 | final int item, final SearchableTag tag) { | |
9c598207 NR |
289 | setWaiting(true); |
290 | new Thread(new Runnable() { | |
c499d79f NR |
291 | @Override |
292 | public void run() { | |
dc3b0033 NR |
293 | try { |
294 | updateSupportType(searchOn); | |
295 | searchPanel.searchTag(tag, page, item); | |
296 | } finally { | |
297 | setWaiting(false); | |
298 | } | |
4357eb54 | 299 | } |
9c598207 | 300 | }).start(); |
bf2b37b0 NR |
301 | } |
302 | ||
4357eb54 NR |
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 | */ | |
741e8467 | 314 | static void inUi(final Runnable run) { |
4357eb54 NR |
315 | if (EventQueue.isDispatchThread()) { |
316 | run.run(); | |
317 | } else { | |
318 | try { | |
319 | EventQueue.invokeAndWait(run); | |
320 | } catch (InterruptedException e) { | |
bf2b37b0 | 321 | error(e); |
4357eb54 | 322 | } catch (InvocationTargetException e) { |
bf2b37b0 | 323 | error(e); |
4357eb54 NR |
324 | } |
325 | } | |
326 | } | |
c499d79f | 327 | |
dc3b0033 NR |
328 | /** |
329 | * An error occurred, inform the user and/or log the error. | |
330 | * | |
331 | * @param e | |
332 | * the error | |
333 | */ | |
741e8467 | 334 | static void error(Exception e) { |
bf2b37b0 NR |
335 | Instance.getTraceHandler().error(e); |
336 | } | |
337 | ||
dc3b0033 NR |
338 | /** |
339 | * An error occurred, inform the user and/or log the error. | |
340 | * | |
341 | * @param e | |
342 | * the error message | |
343 | */ | |
741e8467 NR |
344 | static void error(String e) { |
345 | Instance.getTraceHandler().error(e); | |
346 | } | |
7cc1e743 | 347 | |
9c598207 NR |
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 | ||
dc3b0033 NR |
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 | */ | |
9c598207 | 372 | private void setWaiting(final boolean waiting) { |
c499d79f NR |
373 | inUi(new Runnable() { |
374 | @Override | |
375 | public void run() { | |
741e8467 | 376 | GuiReaderSearchFrame.this.setEnabled(!waiting); |
c499d79f NR |
377 | } |
378 | }); | |
379 | } | |
4357eb54 | 380 | } |