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, NULL allowed
58 public GuiReaderSearchByNamePanel(SupportType supportType
) {
59 setLayout(new BorderLayout());
61 // TODO: check if null really is OK for supportType (must be)
63 setSupportType(supportType
);
67 searchTabs
= new JTabbedPane();
68 searchTabs
.addTab("By name", createByNameSearchPanel());
69 searchTabs
.addTab("By tags", createByTagSearchPanel());
71 add(searchTabs
, BorderLayout
.CENTER
);
74 private JPanel
createByNameSearchPanel() {
75 JPanel byName
= new JPanel(new BorderLayout());
77 keywordsField
= new JTextField();
78 byName
.add(keywordsField
, BorderLayout
.CENTER
);
80 submitKeywords
= new JButton("Search");
81 byName
.add(submitKeywords
, BorderLayout
.EAST
);
83 // TODO: ENTER -> search
85 submitKeywords
.addActionListener(new ActionListener() {
87 public void actionPerformed(ActionEvent e
) {
88 search(keywordsField
.getText(), 0, null);
95 private JPanel
createByTagSearchPanel() {
96 combos
= new ArrayList
<JComboBox
>();
98 JPanel byTag
= new JPanel();
99 tagBars
= new JPanel();
100 tagBars
.setLayout(new BoxLayout(tagBars
, BoxLayout
.Y_AXIS
));
101 byTag
.add(tagBars
, BorderLayout
.NORTH
);
106 public SupportType
getSupportType() {
110 public void setSupportType(SupportType supportType
) {
111 BasicSearchable searchable
= BasicSearchable
.getSearchable(supportType
);
112 if (searchable
== null && supportType
!= null) {
113 throw new java
.lang
.IllegalArgumentException(
114 "Unupported support type: " + supportType
);
117 // TODO: if <>, reset all
118 // if new, set the base tags
120 this.supportType
= supportType
;
121 this.searchable
= searchable
;
124 public int getPage() {
128 public void setPage(int page
) {
129 // TODO: set against maxPage
130 // TODO: update last search?
134 // actions will be fired in UIthread
135 public void addActionListener(ActionListener action
) {
139 public boolean removeActionListener(ActionListener action
) {
140 return actions
.remove(action
);
143 public List
<MetaData
> getStories() {
147 // selected item or 0 if none ! one-based !
148 public int getStoryItem() {
152 private void fireAction(final Runnable inUi
) {
153 GuiReaderSearchFrame
.inUi(new Runnable() {
156 ActionEvent ae
= new ActionEvent(
157 GuiReaderSearchByNamePanel
.this, actionEventId
,
161 if (actionEventId
> ActionEvent
.ACTION_LAST
) {
162 actionEventId
= ActionEvent
.ACTION_FIRST
;
165 for (ActionListener action
: actions
) {
167 action
.actionPerformed(ae
);
168 } catch (Exception e
) {
169 GuiReaderSearchFrame
.error(e
);
180 private void updateSearchBy(final boolean byTag
) {
181 if (byTag
!= this.searchByTags
) {
182 GuiReaderSearchFrame
.inUi(new Runnable() {
186 searchTabs
.setSelectedIndex(0);
188 searchTabs
.setSelectedIndex(1);
196 private void updateKeywords(final String keywords
) {
197 if (!keywords
.equals(this.keywords
)) {
198 GuiReaderSearchFrame
.inUi(new Runnable() {
201 GuiReaderSearchByNamePanel
.this.keywords
= keywords
;
202 keywordsField
.setText(keywords
);
208 // update and reset the tagsbar
209 // can be NULL, for base tags
210 private void updateTags(final SearchableTag tag
) {
211 final List
<SearchableTag
> parents
= new ArrayList
<SearchableTag
>();
212 SearchableTag parent
= (tag
== null) ?
null : tag
;
213 while (parent
!= null) {
215 parent
= parent
.getParent();
218 List
<SearchableTag
> rootTags
= null;
219 SearchableTag selectedRootTag
= null;
220 selectedRootTag
= parents
.isEmpty() ?
null
221 : parents
.get(parents
.size() - 1);
224 rootTags
= searchable
.getTags();
225 } catch (IOException e
) {
226 GuiReaderSearchFrame
.error(e
);
229 final List
<SearchableTag
> rootTagsF
= rootTags
;
230 final SearchableTag selectedRootTagF
= selectedRootTag
;
232 GuiReaderSearchFrame
.inUi(new Runnable() {
235 tagBars
.invalidate();
238 addTagBar(rootTagsF
, selectedRootTagF
);
240 for (int i
= parents
.size() - 1; i
>= 0; i
--) {
241 SearchableTag selectedChild
= null;
243 selectedChild
= parents
.get(i
- 1);
246 SearchableTag parent
= parents
.get(i
);
247 addTagBar(parent
.getChildren(), selectedChild
);
255 // must be quick and no thread change
256 private void addTagBar(List
<SearchableTag
> tags
,
257 final SearchableTag selected
) {
260 final int comboIndex
= combos
.size();
262 final JComboBox combo
= new JComboBox(
263 tags
.toArray(new SearchableTag
[] {}));
264 combo
.setSelectedItem(selected
);
266 final ListCellRenderer basic
= combo
.getRenderer();
268 combo
.setRenderer(new ListCellRenderer() {
270 public Component
getListCellRendererComponent(JList list
,
271 Object value
, int index
, boolean isSelected
,
272 boolean cellHasFocus
) {
274 Object displayValue
= value
;
275 if (value
instanceof SearchableTag
) {
276 displayValue
= ((SearchableTag
) value
).getName();
278 displayValue
= "Select a tag...";
279 cellHasFocus
= false;
283 Component rep
= basic
.getListCellRendererComponent(list
,
284 displayValue
, index
, isSelected
, cellHasFocus
);
287 rep
.setForeground(Color
.GRAY
);
294 combo
.addActionListener(createComboTagAction(comboIndex
));
300 private ActionListener
createComboTagAction(final int comboIndex
) {
301 return new ActionListener() {
303 public void actionPerformed(ActionEvent ae
) {
304 List
<JComboBox
> combos
= GuiReaderSearchByNamePanel
.this.combos
;
305 if (combos
== null || comboIndex
< 0 || comboIndex
>= combos
.size()) {
310 final SearchableTag tag
= (SearchableTag
) combos
.get(comboIndex
)
313 while (comboIndex
+ 1 < combos
.size()) {
314 JComboBox combo
= combos
.remove(comboIndex
+ 1);
315 tagBars
.remove(combo
);
318 new Thread(new Runnable() {
321 final List
<SearchableTag
> children
= getChildrenForTag(tag
);
322 if (children
!= null) {
323 GuiReaderSearchFrame
.inUi(new Runnable() {
326 addTagBar(children
, tag
);
331 if (tag
!= null && tag
.isLeaf()) {
333 GuiReaderSearchByNamePanel
.this.page
= 1;
334 stories
= searchable
.search(tag
, 1);
335 } catch (IOException e
) {
336 GuiReaderSearchFrame
.error(e
);
337 GuiReaderSearchByNamePanel
.this.page
= 0;
338 stories
= new ArrayList
<MetaData
>();
349 // sync, add children of tag, NULL = base tags
350 // return children of the tag or base tags or NULL
351 private List
<SearchableTag
> getChildrenForTag(final SearchableTag tag
) {
352 List
<SearchableTag
> children
= new ArrayList
<SearchableTag
>();
355 List
<SearchableTag
> baseTags
= searchable
.getTags();
357 } catch (IOException e
) {
358 GuiReaderSearchFrame
.error(e
);
362 searchable
.fillTag(tag
);
363 } catch (IOException e
) {
364 GuiReaderSearchFrame
.error(e
);
368 children
= tag
.getChildren();
377 // item 0 = no selection, else = default selection
379 public int search(String keywords
, int item
, Runnable inUi
) {
380 List
<MetaData
> stories
= new ArrayList
<MetaData
>();
383 updateSearchBy(false);
384 updateKeywords(keywords
);
388 maxPage
= searchable
.searchPages(keywords
);
389 } catch (IOException e
) {
390 GuiReaderSearchFrame
.error(e
);
395 stories
= searchable
.search(keywords
, page
);
396 } catch (IOException e
) {
397 GuiReaderSearchFrame
.error(e
);
398 stories
= new ArrayList
<MetaData
>();
401 if (item
> 0 && item
<= stories
.size()) {
403 } else if (item
> 0) {
404 GuiReaderSearchFrame
.error(String
.format(
405 "Story item does not exist: Search [%s], item %d",
410 this.stories
= stories
;
411 this.storyItem
= storyItem
;
417 // tag: null = base tags
419 public int searchTag(SearchableTag tag
, int item
, Runnable inUi
) {
420 List
<MetaData
> stories
= new ArrayList
<MetaData
>();
423 updateSearchBy(true);
429 searchable
.fillTag(tag
);
432 List
<SearchableTag
> subtags
= tag
.getChildren();
433 if (item
> 0 && item
<= subtags
.size()) {
434 SearchableTag subtag
= subtags
.get(item
- 1);
437 searchable
.fillTag(tag
);
438 } catch (IOException e
) {
439 GuiReaderSearchFrame
.error(e
);
441 } else if (item
> 0) {
442 GuiReaderSearchFrame
.error(String
.format(
443 "Tag item does not exist: Tag [%s], item %d",
444 tag
.getFqName(), item
));
448 maxPage
= searchable
.searchPages(tag
);
452 stories
= searchable
.search(tag
, page
);
453 if (item
> 0 && item
<= stories
.size()) {
455 } else if (item
> 0) {
456 GuiReaderSearchFrame
.error(String
.format(
457 "Story item does not exist: Tag [%s], item %d",
458 tag
.getFqName(), item
));
460 } catch (IOException e
) {
461 GuiReaderSearchFrame
.error(e
);
465 } catch (IOException e
) {
466 GuiReaderSearchFrame
.error(e
);
471 this.stories
= stories
;
472 this.storyItem
= storyItem
;
479 * Enables or disables this component, depending on the value of the
480 * parameter <code>b</code>. An enabled component can respond to user input
481 * and generate events. Components are enabled initially by default.
483 * Disabling this component will also affect its children.
486 * If <code>true</code>, this component is enabled; otherwise
487 * this component is disabled
490 public void setEnabled(final boolean waiting
) {
491 GuiReaderSearchFrame
.inUi(new Runnable() {
494 GuiReaderSearchByNamePanel
.super.setEnabled(!waiting
);
495 keywordsField
.setEnabled(!waiting
);
496 submitKeywords
.setEnabled(!waiting
);