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
.JButton
;
14 import javax
.swing
.JComboBox
;
15 import javax
.swing
.JList
;
16 import javax
.swing
.JPanel
;
17 import javax
.swing
.JTabbedPane
;
18 import javax
.swing
.JTextField
;
19 import javax
.swing
.ListCellRenderer
;
21 import be
.nikiroo
.fanfix
.data
.MetaData
;
22 import be
.nikiroo
.fanfix
.searchable
.BasicSearchable
;
23 import be
.nikiroo
.fanfix
.searchable
.SearchableTag
;
24 import be
.nikiroo
.fanfix
.supported
.SupportType
;
27 * This panel represents a search panel that works for keywords and tags based
32 // JCombobox<E> not 1.6 compatible
33 @SuppressWarnings({ "unchecked", "rawtypes" })
34 public class GuiReaderSearchByNamePanel
extends JPanel
{
35 private static final long serialVersionUID
= 1L;
37 private int actionEventId
= ActionEvent
.ACTION_FIRST
;
39 private SupportType supportType
;
40 private BasicSearchable searchable
;
42 private boolean searchByTags
;
44 private String keywords
;
45 private JTabbedPane searchTabs
;
46 private JTextField keywordsField
;
47 private JButton submitKeywords
;
49 private JPanel tagBars
;
50 private List
<JComboBox
> combos
;
51 private JComboBox comboSupportTypes
;
53 private List
<ActionListener
> actions
= new ArrayList
<ActionListener
>();
54 private List
<MetaData
> stories
= new ArrayList
<MetaData
>();
55 private int storyItem
;
57 // will throw illegalArgEx if bad support type
58 public GuiReaderSearchByNamePanel(SupportType supportType
) {
59 setLayout(new BorderLayout());
61 setSupportType(supportType
);
65 searchTabs
= new JTabbedPane();
66 searchTabs
.addTab("By name", createByNameSearchPanel());
67 searchTabs
.addTab("By tags", createByTagSearchPanel());
69 add(searchTabs
, BorderLayout
.CENTER
);
72 private JPanel
createByNameSearchPanel() {
73 JPanel byName
= new JPanel(new BorderLayout());
75 keywordsField
= new JTextField();
76 byName
.add(keywordsField
, BorderLayout
.CENTER
);
78 submitKeywords
= new JButton("Search");
79 byName
.add(submitKeywords
, BorderLayout
.EAST
);
81 // TODO: ENTER -> search
83 submitKeywords
.addActionListener(new ActionListener() {
85 public void actionPerformed(ActionEvent e
) {
86 search(keywordsField
.getText(), 0);
93 private JPanel
createByTagSearchPanel() {
94 combos
= new ArrayList
<JComboBox
>();
96 JPanel byTag
= new JPanel();
97 tagBars
= new JPanel();
98 tagBars
.setLayout(new BoxLayout(tagBars
, BoxLayout
.Y_AXIS
));
99 byTag
.add(tagBars
, BorderLayout
.NORTH
);
104 public SupportType
getSupportType() {
108 public void setSupportType(SupportType supportType
) {
109 BasicSearchable searchable
= BasicSearchable
.getSearchable(supportType
);
110 if (searchable
== null) {
111 throw new java
.lang
.IllegalArgumentException(
112 "Unupported support type: " + supportType
);
115 this.supportType
= supportType
;
116 this.searchable
= searchable
;
119 public int getPage() {
123 public void setPage(int page
) {
124 // TODO: set against maxPage
125 // TODO: update last search?
129 // actions will be fired in UIthread
130 public void addActionListener(ActionListener action
) {
134 public boolean removeActionListener(ActionListener action
) {
135 return actions
.remove(action
);
138 public List
<MetaData
> getStories() {
142 // selected item or 0 if none ! one-based !
143 public int getStoryItem() {
147 private void fireAction() {
148 GuiReaderSearchFrame
.inUi(new Runnable() {
151 ActionEvent ae
= new ActionEvent(
152 GuiReaderSearchByNamePanel
.this, actionEventId
,
156 if (actionEventId
> ActionEvent
.ACTION_LAST
) {
157 actionEventId
= ActionEvent
.ACTION_FIRST
;
160 for (ActionListener action
: actions
) {
162 action
.actionPerformed(ae
);
163 } catch (Exception e
) {
164 GuiReaderSearchFrame
.error(e
);
171 private void updateSearchBy(final boolean byTag
) {
172 if (byTag
!= this.searchByTags
) {
173 GuiReaderSearchFrame
.inUi(new Runnable() {
177 searchTabs
.setSelectedIndex(0);
179 searchTabs
.setSelectedIndex(1);
187 private void updateKeywords(final String keywords
) {
188 if (!keywords
.equals(this.keywords
)) {
189 GuiReaderSearchFrame
.inUi(new Runnable() {
192 GuiReaderSearchByNamePanel
.this.keywords
= keywords
;
193 keywordsField
.setText(keywords
);
199 // update and reset the tagsbar
200 // can be NULL, for base tags
201 private void updateTags(final SearchableTag tag
) {
202 final List
<SearchableTag
> parents
= new ArrayList
<SearchableTag
>();
203 SearchableTag parent
= (tag
== null) ?
null : tag
;
204 while (parent
!= null) {
206 parent
= parent
.getParent();
209 List
<SearchableTag
> rootTags
= null;
210 SearchableTag selectedRootTag
= null;
211 selectedRootTag
= parents
.isEmpty() ?
null
212 : parents
.get(parents
.size() - 1);
215 rootTags
= searchable
.getTags();
216 } catch (IOException e
) {
217 GuiReaderSearchFrame
.error(e
);
220 final List
<SearchableTag
> rootTagsF
= rootTags
;
221 final SearchableTag selectedRootTagF
= selectedRootTag
;
223 GuiReaderSearchFrame
.inUi(new Runnable() {
226 tagBars
.invalidate();
229 addTagBar(rootTagsF
, selectedRootTagF
);
231 for (int i
= parents
.size() - 1; i
>= 0; i
--) {
232 SearchableTag selectedChild
= null;
234 selectedChild
= parents
.get(i
- 1);
237 SearchableTag parent
= parents
.get(i
);
238 addTagBar(parent
.getChildren(), selectedChild
);
246 // must be quick and no thread change
247 private void addTagBar(List
<SearchableTag
> tags
,
248 final SearchableTag selected
) {
251 final int comboIndex
= combos
.size();
253 final JComboBox combo
= new JComboBox(
254 tags
.toArray(new SearchableTag
[] {}));
255 combo
.setSelectedItem(selected
);
257 final ListCellRenderer basic
= combo
.getRenderer();
259 combo
.setRenderer(new ListCellRenderer() {
261 public Component
getListCellRendererComponent(JList list
,
262 Object value
, int index
, boolean isSelected
,
263 boolean cellHasFocus
) {
265 Object displayValue
= value
;
266 if (value
instanceof SearchableTag
) {
267 displayValue
= ((SearchableTag
) value
).getName();
269 displayValue
= "Select a tag...";
270 cellHasFocus
= false;
274 Component rep
= basic
.getListCellRendererComponent(list
,
275 displayValue
, index
, isSelected
, cellHasFocus
);
278 rep
.setForeground(Color
.GRAY
);
285 combo
.addActionListener(createComboTagAction(comboIndex
));
291 private ActionListener
createComboTagAction(final int comboIndex
) {
292 return new ActionListener() {
294 public void actionPerformed(ActionEvent ae
) {
295 List
<JComboBox
> combos
= GuiReaderSearchByNamePanel
.this.combos
;
296 if (combos
== null || comboIndex
< 0 || comboIndex
>= combos
.size()) {
301 final SearchableTag tag
= (SearchableTag
) combos
.get(comboIndex
)
304 while (comboIndex
+ 1 < combos
.size()) {
305 JComboBox combo
= combos
.remove(comboIndex
+ 1);
306 tagBars
.remove(combo
);
309 new Thread(new Runnable() {
312 final List
<SearchableTag
> children
= getChildrenForTag(tag
);
313 if (children
!= null) {
314 GuiReaderSearchFrame
.inUi(new Runnable() {
317 addTagBar(children
, tag
);
322 if (tag
!= null && tag
.isLeaf()) {
324 GuiReaderSearchByNamePanel
.this.page
= 1;
325 stories
= searchable
.search(tag
, 1);
326 } catch (IOException e
) {
327 GuiReaderSearchFrame
.error(e
);
328 GuiReaderSearchByNamePanel
.this.page
= 0;
329 stories
= new ArrayList
<MetaData
>();
340 // sync, add children of tag, NULL = base tags
341 // return children of the tag or base tags or NULL
342 private List
<SearchableTag
> getChildrenForTag(final SearchableTag tag
) {
343 List
<SearchableTag
> children
= new ArrayList
<SearchableTag
>();
346 List
<SearchableTag
> baseTags
= searchable
.getTags();
348 } catch (IOException e
) {
349 GuiReaderSearchFrame
.error(e
);
353 searchable
.fillTag(tag
);
354 } catch (IOException e
) {
355 GuiReaderSearchFrame
.error(e
);
359 children
= tag
.getChildren();
368 // item 0 = no selection, else = default selection
370 public int search(String keywords
, int item
) {
371 List
<MetaData
> stories
= new ArrayList
<MetaData
>();
374 updateSearchBy(false);
375 updateKeywords(keywords
);
379 maxPage
= searchable
.searchPages(keywords
);
380 } catch (IOException e
) {
381 GuiReaderSearchFrame
.error(e
);
386 stories
= searchable
.search(keywords
, page
);
387 } catch (IOException e
) {
388 GuiReaderSearchFrame
.error(e
);
389 stories
= new ArrayList
<MetaData
>();
392 if (item
> 0 && item
<= stories
.size()) {
394 } else if (item
> 0) {
395 GuiReaderSearchFrame
.error(String
.format(
396 "Story item does not exist: Search [%s], item %d",
401 this.stories
= stories
;
402 this.storyItem
= storyItem
;
408 // tag: null = base tags
410 public int searchTag(SearchableTag tag
, int item
) {
411 List
<MetaData
> stories
= new ArrayList
<MetaData
>();
414 updateSearchBy(true);
420 searchable
.fillTag(tag
);
423 List
<SearchableTag
> subtags
= tag
.getChildren();
424 if (item
> 0 && item
<= subtags
.size()) {
425 SearchableTag subtag
= subtags
.get(item
- 1);
428 searchable
.fillTag(tag
);
429 } catch (IOException e
) {
430 GuiReaderSearchFrame
.error(e
);
432 } else if (item
> 0) {
433 GuiReaderSearchFrame
.error(String
.format(
434 "Tag item does not exist: Tag [%s], item %d",
435 tag
.getFqName(), item
));
439 maxPage
= searchable
.searchPages(tag
);
443 stories
= searchable
.search(tag
, page
);
444 if (item
> 0 && item
<= stories
.size()) {
446 } else if (item
> 0) {
447 GuiReaderSearchFrame
.error(String
.format(
448 "Story item does not exist: Tag [%s], item %d",
449 tag
.getFqName(), item
));
451 } catch (IOException e
) {
452 GuiReaderSearchFrame
.error(e
);
456 } catch (IOException e
) {
457 GuiReaderSearchFrame
.error(e
);
462 this.stories
= stories
;
463 this.storyItem
= storyItem
;
470 * Enables or disables this component, depending on the value of the
471 * parameter <code>b</code>. An enabled component can respond to user input
472 * and generate events. Components are enabled initially by default.
474 * Disabling this component will also affect its children.
477 * If <code>true</code>, this component is enabled; otherwise
478 * this component is disabled
481 public void setEnabled(final boolean waiting
) {
482 GuiReaderSearchFrame
.inUi(new Runnable() {
485 GuiReaderSearchByNamePanel
.super.setEnabled(!waiting
);
486 keywordsField
.setEnabled(!waiting
);
487 submitKeywords
.setEnabled(!waiting
);