1 package be
.nikiroo
.fanfix
.reader
.ui
;
3 import java
.awt
.BorderLayout
;
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
;
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
;
18 import be
.nikiroo
.fanfix
.data
.MetaData
;
19 import be
.nikiroo
.fanfix
.searchable
.BasicSearchable
;
20 import be
.nikiroo
.fanfix
.searchable
.SearchableTag
;
23 * This panel represents a search panel that works for keywords and tags based
28 // JCombobox<E> not 1.6 compatible
29 @SuppressWarnings({ "unchecked", "rawtypes" })
30 public class GuiReaderSearchByTagPanel
extends JPanel
{
31 private static final long serialVersionUID
= 1L;
33 private BasicSearchable searchable
;
34 private Runnable fireEvent
;
36 private SearchableTag currentTag
;
37 private JPanel tagBars
;
38 private List
<JComboBox
> combos
;
42 private List
<MetaData
> stories
= new ArrayList
<MetaData
>();
43 private int storyItem
;
45 public GuiReaderSearchByTagPanel(Runnable fireEvent
) {
46 setLayout(new BorderLayout());
48 this.fireEvent
= fireEvent
;
49 combos
= new ArrayList
<JComboBox
>();
53 tagBars
= new JPanel();
54 tagBars
.setLayout(new BoxLayout(tagBars
, BoxLayout
.Y_AXIS
));
55 add(tagBars
, BorderLayout
.NORTH
);
59 * The {@link BasicSearchable} object use for the searches themselves.
61 * Can be NULL, but no searches will work.
66 public void setSearchable(BasicSearchable searchable
) {
67 this.searchable
= searchable
;
71 stories
= new ArrayList
<MetaData
>();
75 public int getPage() {
79 public int getMaxPage() {
83 public SearchableTag
getCurrentTag() {
87 public List
<MetaData
> getStories() {
91 // selected item or 0 if none ! one-based !
92 public int getStoryItem() {
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) {
103 parent
= parent
.getParent();
106 List
<SearchableTag
> rootTags
= new ArrayList
<SearchableTag
>();
107 SearchableTag selectedRootTag
= null;
108 selectedRootTag
= parents
.isEmpty() ?
null : parents
109 .get(parents
.size() - 1);
111 if (searchable
!= null) {
113 rootTags
= searchable
.getTags();
114 } catch (IOException e
) {
115 GuiReaderSearchFrame
.error(e
);
119 final List
<SearchableTag
> rootTagsF
= rootTags
;
120 final SearchableTag selectedRootTagF
= selectedRootTag
;
122 GuiReaderSearchFrame
.inUi(new Runnable() {
125 tagBars
.invalidate();
128 addTagBar(rootTagsF
, selectedRootTagF
);
130 for (int i
= parents
.size() - 1; i
>= 0; i
--) {
131 SearchableTag selectedChild
= null;
133 selectedChild
= parents
.get(i
- 1);
136 SearchableTag parent
= parents
.get(i
);
137 addTagBar(parent
.getChildren(), selectedChild
);
145 // must be quick and no thread change
146 private void addTagBar(List
<SearchableTag
> tags
,
147 final SearchableTag selected
) {
150 final int comboIndex
= combos
.size();
152 final JComboBox combo
= new JComboBox(
153 tags
.toArray(new SearchableTag
[] {}));
154 combo
.setSelectedItem(selected
);
156 final ListCellRenderer basic
= combo
.getRenderer();
158 combo
.setRenderer(new ListCellRenderer() {
160 public Component
getListCellRendererComponent(JList list
,
161 Object value
, int index
, boolean isSelected
,
162 boolean cellHasFocus
) {
164 Object displayValue
= value
;
165 if (value
instanceof SearchableTag
) {
166 displayValue
= ((SearchableTag
) value
).getName();
168 displayValue
= "Select a tag...";
169 cellHasFocus
= false;
173 Component rep
= basic
.getListCellRendererComponent(list
,
174 displayValue
, index
, isSelected
, cellHasFocus
);
177 rep
.setForeground(Color
.GRAY
);
184 combo
.addActionListener(createComboTagAction(comboIndex
));
190 private ActionListener
createComboTagAction(final int comboIndex
) {
191 return new ActionListener() {
193 public void actionPerformed(ActionEvent ae
) {
194 List
<JComboBox
> combos
= GuiReaderSearchByTagPanel
.this.combos
;
195 if (combos
== null || comboIndex
< 0
196 || comboIndex
>= combos
.size()) {
201 final SearchableTag tag
= (SearchableTag
) combos
202 .get(comboIndex
).getSelectedItem();
204 while (comboIndex
+ 1 < combos
.size()) {
205 JComboBox combo
= combos
.remove(comboIndex
+ 1);
206 tagBars
.remove(combo
);
209 new Thread(new Runnable() {
212 final List
<SearchableTag
> children
= getChildrenForTag(tag
);
213 if (children
!= null) {
214 GuiReaderSearchFrame
.inUi(new Runnable() {
217 addTagBar(children
, tag
);
222 if (tag
!= null && tag
.isLeaf()) {
225 searchable
.fillTag(tag
);
227 stories
= searchable
.search(tag
, 1);
228 maxPage
= searchable
.searchPages(tag
);
229 } catch (IOException e
) {
230 GuiReaderSearchFrame
.error(e
);
233 stories
= new ArrayList
<MetaData
>();
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
>();
250 List
<SearchableTag
> baseTags
= searchable
.getTags();
252 } catch (IOException e
) {
253 GuiReaderSearchFrame
.error(e
);
257 searchable
.fillTag(tag
);
258 } catch (IOException e
) {
259 GuiReaderSearchFrame
.error(e
);
263 children
= tag
.getChildren();
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
>();
285 searchable
.fillTag(tag
);
288 List
<SearchableTag
> subtags
= tag
.getChildren();
289 if (item
> 0 && item
<= subtags
.size()) {
290 SearchableTag subtag
= subtags
.get(item
- 1);
293 searchable
.fillTag(tag
);
294 } catch (IOException e
) {
295 GuiReaderSearchFrame
.error(e
);
297 } else if (item
> 0) {
298 GuiReaderSearchFrame
.error(String
.format(
299 "Tag item does not exist: Tag [%s], item %d",
300 tag
.getFqName(), item
));
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
);
312 stories
= searchable
.search(tag
, page
);
313 if (item
> 0 && item
<= stories
.size()) {
315 } else if (item
> 0) {
318 .format("Story item does not exist: Tag [%s], item %d",
319 tag
.getFqName(), item
));
321 } catch (IOException e
) {
322 GuiReaderSearchFrame
.error(e
);
325 } catch (IOException e
) {
326 GuiReaderSearchFrame
.error(e
);
331 this.stories
= stories
;
332 this.storyItem
= storyItem
;
334 this.maxPage
= maxPage
;
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.
342 * Disabling this component will also affect its children.
345 * If <code>true</code>, this component is enabled; otherwise
346 * this component is disabled
349 public void setEnabled(boolean b
) {
351 tagBars
.setEnabled(b
);
352 for (JComboBox combo
: combos
) {