GUI search: forgot 2 filesgit add src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchBy*!
[fanfix.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderSearchByTagPanel.java
1 package be.nikiroo.fanfix.reader.ui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Component;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.io.IOException;
9 import java.util.ArrayList;
10 import java.util.List;
11
12 import javax.swing.BoxLayout;
13 import javax.swing.JComboBox;
14 import javax.swing.JList;
15 import javax.swing.JPanel;
16 import javax.swing.ListCellRenderer;
17
18 import be.nikiroo.fanfix.data.MetaData;
19 import be.nikiroo.fanfix.searchable.BasicSearchable;
20 import be.nikiroo.fanfix.searchable.SearchableTag;
21
22 /**
23 * This panel represents a search panel that works for keywords and tags based
24 * searches.
25 *
26 * @author niki
27 */
28 // JCombobox<E> not 1.6 compatible
29 @SuppressWarnings({ "unchecked", "rawtypes" })
30 public class GuiReaderSearchByTagPanel extends JPanel {
31 private static final long serialVersionUID = 1L;
32
33 private BasicSearchable searchable;
34 private Runnable fireEvent;
35
36 private SearchableTag currentTag;
37 private JPanel tagBars;
38 private List<JComboBox> combos;
39
40 private int page;
41 private int maxPage;
42 private List<MetaData> stories = new ArrayList<MetaData>();
43 private int storyItem;
44
45 public GuiReaderSearchByTagPanel(Runnable fireEvent) {
46 setLayout(new BorderLayout());
47
48 this.fireEvent = fireEvent;
49 combos = new ArrayList<JComboBox>();
50 page = 1;
51 maxPage = -1;
52
53 tagBars = new JPanel();
54 tagBars.setLayout(new BoxLayout(tagBars, BoxLayout.Y_AXIS));
55 add(tagBars, BorderLayout.NORTH);
56 }
57
58 /**
59 * The {@link BasicSearchable} object use for the searches themselves.
60 * <p>
61 * Can be NULL, but no searches will work.
62 *
63 * @param searchable
64 * the new searchable
65 */
66 public void setSearchable(BasicSearchable searchable) {
67 this.searchable = searchable;
68 page = 1;
69 maxPage = -1;
70 storyItem = 0;
71 stories = new ArrayList<MetaData>();
72 updateTags(null);
73 }
74
75 public int getPage() {
76 return page;
77 }
78
79 public int getMaxPage() {
80 return maxPage;
81 }
82
83 public SearchableTag getCurrentTag() {
84 return currentTag;
85 }
86
87 public List<MetaData> getStories() {
88 return stories;
89 }
90
91 // selected item or 0 if none ! one-based !
92 public int getStoryItem() {
93 return storyItem;
94 }
95
96 // update and reset the tagsbar
97 // can be NULL, for base tags
98 private void updateTags(final SearchableTag tag) {
99 final List<SearchableTag> parents = new ArrayList<SearchableTag>();
100 SearchableTag parent = (tag == null) ? null : tag;
101 while (parent != null) {
102 parents.add(parent);
103 parent = parent.getParent();
104 }
105
106 List<SearchableTag> rootTags = new ArrayList<SearchableTag>();
107 SearchableTag selectedRootTag = null;
108 selectedRootTag = parents.isEmpty() ? null : parents
109 .get(parents.size() - 1);
110
111 if (searchable != null) {
112 try {
113 rootTags = searchable.getTags();
114 } catch (IOException e) {
115 GuiReaderSearchFrame.error(e);
116 }
117 }
118
119 final List<SearchableTag> rootTagsF = rootTags;
120 final SearchableTag selectedRootTagF = selectedRootTag;
121
122 GuiReaderSearchFrame.inUi(new Runnable() {
123 @Override
124 public void run() {
125 tagBars.invalidate();
126 tagBars.removeAll();
127
128 addTagBar(rootTagsF, selectedRootTagF);
129
130 for (int i = parents.size() - 1; i >= 0; i--) {
131 SearchableTag selectedChild = null;
132 if (i > 0) {
133 selectedChild = parents.get(i - 1);
134 }
135
136 SearchableTag parent = parents.get(i);
137 addTagBar(parent.getChildren(), selectedChild);
138 }
139
140 tagBars.validate();
141 }
142 });
143 }
144
145 // must be quick and no thread change
146 private void addTagBar(List<SearchableTag> tags,
147 final SearchableTag selected) {
148 tags.add(0, null);
149
150 final int comboIndex = combos.size();
151
152 final JComboBox combo = new JComboBox(
153 tags.toArray(new SearchableTag[] {}));
154 combo.setSelectedItem(selected);
155
156 final ListCellRenderer basic = combo.getRenderer();
157
158 combo.setRenderer(new ListCellRenderer() {
159 @Override
160 public Component getListCellRendererComponent(JList list,
161 Object value, int index, boolean isSelected,
162 boolean cellHasFocus) {
163
164 Object displayValue = value;
165 if (value instanceof SearchableTag) {
166 displayValue = ((SearchableTag) value).getName();
167 } else {
168 displayValue = "Select a tag...";
169 cellHasFocus = false;
170 isSelected = false;
171 }
172
173 Component rep = basic.getListCellRendererComponent(list,
174 displayValue, index, isSelected, cellHasFocus);
175
176 if (value == null) {
177 rep.setForeground(Color.GRAY);
178 }
179
180 return rep;
181 }
182 });
183
184 combo.addActionListener(createComboTagAction(comboIndex));
185
186 combos.add(combo);
187 tagBars.add(combo);
188 }
189
190 private ActionListener createComboTagAction(final int comboIndex) {
191 return new ActionListener() {
192 @Override
193 public void actionPerformed(ActionEvent ae) {
194 List<JComboBox> combos = GuiReaderSearchByTagPanel.this.combos;
195 if (combos == null || comboIndex < 0
196 || comboIndex >= combos.size()) {
197 return;
198 }
199
200 // Tag can be NULL
201 final SearchableTag tag = (SearchableTag) combos
202 .get(comboIndex).getSelectedItem();
203
204 while (comboIndex + 1 < combos.size()) {
205 JComboBox combo = combos.remove(comboIndex + 1);
206 tagBars.remove(combo);
207 }
208
209 new Thread(new Runnable() {
210 @Override
211 public void run() {
212 final List<SearchableTag> children = getChildrenForTag(tag);
213 if (children != null) {
214 GuiReaderSearchFrame.inUi(new Runnable() {
215 @Override
216 public void run() {
217 addTagBar(children, tag);
218 }
219 });
220 }
221
222 if (tag != null && tag.isLeaf()) {
223 storyItem = 0;
224 try {
225 searchable.fillTag(tag);
226 page = 1;
227 stories = searchable.search(tag, 1);
228 maxPage = searchable.searchPages(tag);
229 } catch (IOException e) {
230 GuiReaderSearchFrame.error(e);
231 page = 0;
232 maxPage = -1;
233 stories = new ArrayList<MetaData>();
234 }
235
236 fireEvent.run();
237 }
238 }
239 }).start();
240 }
241 };
242 }
243
244 // sync, add children of tag, NULL = base tags
245 // return children of the tag or base tags or NULL
246 private List<SearchableTag> getChildrenForTag(final SearchableTag tag) {
247 List<SearchableTag> children = new ArrayList<SearchableTag>();
248 if (tag == null) {
249 try {
250 List<SearchableTag> baseTags = searchable.getTags();
251 children = baseTags;
252 } catch (IOException e) {
253 GuiReaderSearchFrame.error(e);
254 }
255 } else {
256 try {
257 searchable.fillTag(tag);
258 } catch (IOException e) {
259 GuiReaderSearchFrame.error(e);
260 }
261
262 if (!tag.isLeaf()) {
263 children = tag.getChildren();
264 } else {
265 children = null;
266 }
267 }
268
269 return children;
270 }
271
272 // slow
273 // tag: null = base tags
274 // throw if page > max, but only if stories
275 public void searchTag(SearchableTag tag, int page, int item) {
276 List<MetaData> stories = new ArrayList<MetaData>();
277 int storyItem = 0;
278
279 currentTag = tag;
280 updateTags(tag);
281
282 int maxPage = -1;
283 if (tag != null) {
284 try {
285 searchable.fillTag(tag);
286
287 if (!tag.isLeaf()) {
288 List<SearchableTag> subtags = tag.getChildren();
289 if (item > 0 && item <= subtags.size()) {
290 SearchableTag subtag = subtags.get(item - 1);
291 try {
292 tag = subtag;
293 searchable.fillTag(tag);
294 } catch (IOException e) {
295 GuiReaderSearchFrame.error(e);
296 }
297 } else if (item > 0) {
298 GuiReaderSearchFrame.error(String.format(
299 "Tag item does not exist: Tag [%s], item %d",
300 tag.getFqName(), item));
301 }
302 }
303
304 maxPage = searchable.searchPages(tag);
305 if (page > 0 && tag.isLeaf()) {
306 if (maxPage >= 0 && (page <= 0 || page > maxPage)) {
307 throw new IndexOutOfBoundsException("Page " + page
308 + " out of " + maxPage);
309 }
310
311 try {
312 stories = searchable.search(tag, page);
313 if (item > 0 && item <= stories.size()) {
314 storyItem = item;
315 } else if (item > 0) {
316 GuiReaderSearchFrame
317 .error(String
318 .format("Story item does not exist: Tag [%s], item %d",
319 tag.getFqName(), item));
320 }
321 } catch (IOException e) {
322 GuiReaderSearchFrame.error(e);
323 }
324 }
325 } catch (IOException e) {
326 GuiReaderSearchFrame.error(e);
327 maxPage = 0;
328 }
329 }
330
331 this.stories = stories;
332 this.storyItem = storyItem;
333 this.page = page;
334 this.maxPage = maxPage;
335 }
336
337 /**
338 * Enables or disables this component, depending on the value of the
339 * parameter <code>b</code>. An enabled component can respond to user input
340 * and generate events. Components are enabled initially by default.
341 * <p>
342 * Disabling this component will also affect its children.
343 *
344 * @param b
345 * If <code>true</code>, this component is enabled; otherwise
346 * this component is disabled
347 */
348 @Override
349 public void setEnabled(boolean b) {
350 super.setEnabled(b);
351 tagBars.setEnabled(b);
352 for (JComboBox combo : combos) {
353 combo.setEnabled(b);
354 }
355 }
356 }