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
.awt
.event
.KeyAdapter
;
9 import java
.awt
.event
.KeyEvent
;
10 import java
.io
.IOException
;
11 import java
.util
.ArrayList
;
12 import java
.util
.List
;
14 import javax
.swing
.BoxLayout
;
15 import javax
.swing
.JButton
;
16 import javax
.swing
.JComboBox
;
17 import javax
.swing
.JList
;
18 import javax
.swing
.JPanel
;
19 import javax
.swing
.JTabbedPane
;
20 import javax
.swing
.JTextField
;
21 import javax
.swing
.ListCellRenderer
;
23 import be
.nikiroo
.fanfix
.data
.MetaData
;
24 import be
.nikiroo
.fanfix
.searchable
.BasicSearchable
;
25 import be
.nikiroo
.fanfix
.searchable
.SearchableTag
;
26 import be
.nikiroo
.fanfix
.supported
.SupportType
;
29 * This panel represents a search panel that works for keywords and tags based
34 // JCombobox<E> not 1.6 compatible
35 @SuppressWarnings({ "unchecked", "rawtypes" })
36 public class GuiReaderSearchByNamePanel
extends JPanel
{
37 private static final long serialVersionUID
= 1L;
39 private int actionEventId
= ActionEvent
.ACTION_FIRST
;
41 private BasicSearchable searchable
;
42 private boolean searchByTags
;
46 private JTabbedPane searchTabs
;
48 private JTextField keywordsField
;
49 private JButton submitKeywords
;
51 private SearchableTag currentTag
;
52 private JPanel tagBars
;
53 private List
<JComboBox
> combos
;
55 private List
<ActionListener
> actions
= new ArrayList
<ActionListener
>();
56 private List
<MetaData
> stories
= new ArrayList
<MetaData
>();
57 private int storyItem
;
59 // will throw illegalArgEx if bad support type, NULL allowed
60 public GuiReaderSearchByNamePanel(final SupportType supportType
,
61 final Runnable inUi
) {
62 setLayout(new BorderLayout());
69 searchTabs
= new JTabbedPane();
70 searchTabs
.addTab("By name", createByNameSearchPanel());
71 searchTabs
.addTab("By tags", createByTagSearchPanel());
73 add(searchTabs
, BorderLayout
.CENTER
);
74 updateSearchBy(searchByTags
);
76 // TODO: check if null really is OK for supportType (must be)
77 new Thread(new Runnable() {
80 setSupportType(supportType
, inUi
);
85 private JPanel
createByNameSearchPanel() {
86 JPanel byName
= new JPanel(new BorderLayout());
88 keywordsField
= new JTextField();
89 byName
.add(keywordsField
, BorderLayout
.CENTER
);
91 submitKeywords
= new JButton("Search");
92 byName
.add(submitKeywords
, BorderLayout
.EAST
);
94 // TODO: ENTER -> search
96 keywordsField
.addKeyListener(new KeyAdapter() {
98 public void keyReleased(KeyEvent e
) {
99 if (e
.getKeyCode() == KeyEvent
.VK_ENTER
) {
100 search(keywordsField
.getText(), 1, 0, null);
102 super.keyReleased(e
);
107 submitKeywords
.addActionListener(new ActionListener() {
109 public void actionPerformed(ActionEvent e
) {
110 search(keywordsField
.getText(), 1, 0, null);
117 private JPanel
createByTagSearchPanel() {
118 combos
= new ArrayList
<JComboBox
>();
120 JPanel byTag
= new JPanel();
121 tagBars
= new JPanel();
122 tagBars
.setLayout(new BoxLayout(tagBars
, BoxLayout
.Y_AXIS
));
123 byTag
.add(tagBars
, BorderLayout
.NORTH
);
129 public void setSupportType(SupportType supportType
, Runnable inUi
) {
130 BasicSearchable searchable
= BasicSearchable
.getSearchable(supportType
);
131 if (searchable
== null && supportType
!= null) {
132 throw new java
.lang
.IllegalArgumentException(
133 "Unupported support type: " + supportType
);
136 this.searchable
= searchable
;
138 searchTag(null, 1, 0, inUi
);
141 public int getPage() {
145 public int getMaxPage() {
149 // throw outOfBounds if needed
150 public void setPage(int page
, Runnable inUi
) {
152 searchTag(currentTag
, page
, 0, inUi
);
154 search(keywordsField
.getText(), page
, 0, inUi
);
158 // actions will be fired in UIthread
159 public void addActionListener(ActionListener action
) {
163 public boolean removeActionListener(ActionListener action
) {
164 return actions
.remove(action
);
167 public List
<MetaData
> getStories() {
171 // selected item or 0 if none ! one-based !
172 public int getStoryItem() {
176 private void fireAction(final Runnable inUi
) {
177 GuiReaderSearchFrame
.inUi(new Runnable() {
180 ActionEvent ae
= new ActionEvent(
181 GuiReaderSearchByNamePanel
.this, actionEventId
,
185 if (actionEventId
> ActionEvent
.ACTION_LAST
) {
186 actionEventId
= ActionEvent
.ACTION_FIRST
;
189 for (ActionListener action
: actions
) {
191 action
.actionPerformed(ae
);
192 } catch (Exception e
) {
193 GuiReaderSearchFrame
.error(e
);
204 private void updateSearchBy(final boolean byTag
) {
205 GuiReaderSearchFrame
.inUi(new Runnable() {
209 searchTabs
.setSelectedIndex(0);
211 searchTabs
.setSelectedIndex(1);
218 private void updateKeywords(final String keywords
) {
219 if (!keywords
.equals(keywordsField
.getText())) {
220 GuiReaderSearchFrame
.inUi(new Runnable() {
223 keywordsField
.setText(keywords
);
229 // update and reset the tagsbar
230 // can be NULL, for base tags
231 private void updateTags(final SearchableTag tag
) {
232 final List
<SearchableTag
> parents
= new ArrayList
<SearchableTag
>();
233 SearchableTag parent
= (tag
== null) ?
null : tag
;
234 while (parent
!= null) {
236 parent
= parent
.getParent();
239 List
<SearchableTag
> rootTags
= null;
240 SearchableTag selectedRootTag
= null;
241 selectedRootTag
= parents
.isEmpty() ?
null : parents
242 .get(parents
.size() - 1);
245 rootTags
= searchable
.getTags();
246 } catch (IOException e
) {
247 GuiReaderSearchFrame
.error(e
);
250 final List
<SearchableTag
> rootTagsF
= rootTags
;
251 final SearchableTag selectedRootTagF
= selectedRootTag
;
253 GuiReaderSearchFrame
.inUi(new Runnable() {
256 tagBars
.invalidate();
259 addTagBar(rootTagsF
, selectedRootTagF
);
261 for (int i
= parents
.size() - 1; i
>= 0; i
--) {
262 SearchableTag selectedChild
= null;
264 selectedChild
= parents
.get(i
- 1);
267 SearchableTag parent
= parents
.get(i
);
268 addTagBar(parent
.getChildren(), selectedChild
);
276 // must be quick and no thread change
277 private void addTagBar(List
<SearchableTag
> tags
,
278 final SearchableTag selected
) {
281 final int comboIndex
= combos
.size();
283 final JComboBox combo
= new JComboBox(
284 tags
.toArray(new SearchableTag
[] {}));
285 combo
.setSelectedItem(selected
);
287 final ListCellRenderer basic
= combo
.getRenderer();
289 combo
.setRenderer(new ListCellRenderer() {
291 public Component
getListCellRendererComponent(JList list
,
292 Object value
, int index
, boolean isSelected
,
293 boolean cellHasFocus
) {
295 Object displayValue
= value
;
296 if (value
instanceof SearchableTag
) {
297 displayValue
= ((SearchableTag
) value
).getName();
299 displayValue
= "Select a tag...";
300 cellHasFocus
= false;
304 Component rep
= basic
.getListCellRendererComponent(list
,
305 displayValue
, index
, isSelected
, cellHasFocus
);
308 rep
.setForeground(Color
.GRAY
);
315 combo
.addActionListener(createComboTagAction(comboIndex
));
321 private ActionListener
createComboTagAction(final int comboIndex
) {
322 return new ActionListener() {
324 public void actionPerformed(ActionEvent ae
) {
325 List
<JComboBox
> combos
= GuiReaderSearchByNamePanel
.this.combos
;
326 if (combos
== null || comboIndex
< 0
327 || comboIndex
>= combos
.size()) {
332 final SearchableTag tag
= (SearchableTag
) combos
333 .get(comboIndex
).getSelectedItem();
335 while (comboIndex
+ 1 < combos
.size()) {
336 JComboBox combo
= combos
.remove(comboIndex
+ 1);
337 tagBars
.remove(combo
);
340 new Thread(new Runnable() {
343 final List
<SearchableTag
> children
= getChildrenForTag(tag
);
344 if (children
!= null) {
345 GuiReaderSearchFrame
.inUi(new Runnable() {
348 addTagBar(children
, tag
);
353 if (tag
!= null && tag
.isLeaf()) {
355 GuiReaderSearchByNamePanel
.this.page
= 1;
356 stories
= searchable
.search(tag
, 1);
357 } catch (IOException e
) {
358 GuiReaderSearchFrame
.error(e
);
359 GuiReaderSearchByNamePanel
.this.page
= 0;
360 stories
= new ArrayList
<MetaData
>();
371 // sync, add children of tag, NULL = base tags
372 // return children of the tag or base tags or NULL
373 private List
<SearchableTag
> getChildrenForTag(final SearchableTag tag
) {
374 List
<SearchableTag
> children
= new ArrayList
<SearchableTag
>();
377 List
<SearchableTag
> baseTags
= searchable
.getTags();
379 } catch (IOException e
) {
380 GuiReaderSearchFrame
.error(e
);
384 searchable
.fillTag(tag
);
385 } catch (IOException e
) {
386 GuiReaderSearchFrame
.error(e
);
390 children
= tag
.getChildren();
399 // item 0 = no selection, else = default selection
400 // throw if page > max
401 public void search(String keywords
, int page
, int item
, Runnable inUi
) {
402 List
<MetaData
> stories
= new ArrayList
<MetaData
>();
405 updateSearchBy(false);
406 updateKeywords(keywords
);
410 maxPage
= searchable
.searchPages(keywords
);
411 } catch (IOException e
) {
412 GuiReaderSearchFrame
.error(e
);
416 if (maxPage
>= 0 && (page
<= 0 || page
> maxPage
)) {
417 throw new IndexOutOfBoundsException("Page " + page
+ " out of "
422 stories
= searchable
.search(keywords
, page
);
423 } catch (IOException e
) {
424 GuiReaderSearchFrame
.error(e
);
425 stories
= new ArrayList
<MetaData
>();
428 if (item
> 0 && item
<= stories
.size()) {
430 } else if (item
> 0) {
431 GuiReaderSearchFrame
.error(String
.format(
432 "Story item does not exist: Search [%s], item %d",
438 this.stories
= stories
;
439 this.storyItem
= storyItem
;
440 this.maxPage
= maxPage
;
446 // tag: null = base tags
447 // throw if page > max, but only if stories
448 public void searchTag(SearchableTag tag
, int page
, int item
, Runnable inUi
) {
449 List
<MetaData
> stories
= new ArrayList
<MetaData
>();
453 updateSearchBy(true);
459 searchable
.fillTag(tag
);
462 List
<SearchableTag
> subtags
= tag
.getChildren();
463 if (item
> 0 && item
<= subtags
.size()) {
464 SearchableTag subtag
= subtags
.get(item
- 1);
467 searchable
.fillTag(tag
);
468 } catch (IOException e
) {
469 GuiReaderSearchFrame
.error(e
);
471 } else if (item
> 0) {
472 GuiReaderSearchFrame
.error(String
.format(
473 "Tag item does not exist: Tag [%s], item %d",
474 tag
.getFqName(), item
));
478 maxPage
= searchable
.searchPages(tag
);
479 if (page
> 0 && tag
.isLeaf()) {
480 if (maxPage
>= 0 && (page
<= 0 || page
> maxPage
)) {
481 throw new IndexOutOfBoundsException("Page " + page
482 + " out of " + maxPage
);
486 stories
= searchable
.search(tag
, page
);
487 if (item
> 0 && item
<= stories
.size()) {
489 } else if (item
> 0) {
492 .format("Story item does not exist: Tag [%s], item %d",
493 tag
.getFqName(), item
));
495 } catch (IOException e
) {
496 GuiReaderSearchFrame
.error(e
);
499 } catch (IOException e
) {
500 GuiReaderSearchFrame
.error(e
);
505 this.stories
= stories
;
506 this.storyItem
= storyItem
;
508 this.maxPage
= maxPage
;
514 * Enables or disables this component, depending on the value of the
515 * parameter <code>b</code>. An enabled component can respond to user input
516 * and generate events. Components are enabled initially by default.
518 * Disabling this component will also affect its children.
521 * If <code>true</code>, this component is enabled; otherwise
522 * this component is disabled
525 public void setEnabled(final boolean enabled
) {
526 GuiReaderSearchFrame
.inUi(new Runnable() {
529 GuiReaderSearchByNamePanel
.super.setEnabled(enabled
);
530 searchTabs
.setEnabled(enabled
);
531 keywordsField
.setEnabled(enabled
);
532 submitKeywords
.setEnabled(enabled
);
533 tagBars
.setEnabled(enabled
);
534 for (JComboBox combo
: combos
) {
535 combo
.setEnabled(enabled
);