1 package be
.nikiroo
.fanfix
.reader
.ui
;
3 import java
.awt
.BorderLayout
;
5 import java
.awt
.Component
;
6 import java
.awt
.Graphics
;
7 import java
.awt
.Rectangle
;
8 import java
.awt
.event
.ActionListener
;
9 import java
.awt
.event
.ComponentAdapter
;
10 import java
.awt
.event
.ComponentEvent
;
11 import java
.awt
.event
.FocusAdapter
;
12 import java
.awt
.event
.FocusEvent
;
13 import java
.awt
.event
.KeyAdapter
;
14 import java
.awt
.event
.KeyEvent
;
15 import java
.util
.ArrayList
;
16 import java
.util
.List
;
18 import javax
.swing
.JLabel
;
19 import javax
.swing
.JPanel
;
21 import be
.nikiroo
.fanfix
.bundles
.StringIdGui
;
22 import be
.nikiroo
.fanfix
.reader
.ui
.GuiReaderBook
.BookActionListener
;
23 import be
.nikiroo
.utils
.ui
.WrapLayout
;
26 * A group of {@link GuiReaderBook}s for display.
30 public class GuiReaderGroup
extends JPanel
{
31 private static final long serialVersionUID
= 1L;
32 private BookActionListener action
;
33 private Color backgroundColor
;
34 private Color backgroundColorDef
;
35 private Color backgroundColorDefPane
;
36 private GuiReader reader
;
37 private List
<GuiReaderBookInfo
> infos
;
38 private List
<GuiReaderBook
> books
;
40 private JLabel titleLabel
;
41 private boolean words
; // words or authors (secondary info on books)
42 private int itemsPerLine
;
45 * Create a new {@link GuiReaderGroup}.
48 * the {@link GuiReaderBook} used to probe some information about
51 * the title of this group (can be NULL for "no title", an empty
52 * {@link String} will trigger a default title for empty groups)
53 * @param backgroundColor
54 * the background colour to use (or NULL for default)
56 public GuiReaderGroup(GuiReader reader
, String title
, Color backgroundColor
) {
59 this.pane
= new JPanel();
60 pane
.setLayout(new WrapLayout(WrapLayout
.LEADING
, 5, 5));
62 this.backgroundColorDef
= getBackground();
63 this.backgroundColorDefPane
= pane
.getBackground();
64 setBackground(backgroundColor
);
66 setLayout(new BorderLayout(0, 10));
73 add(pane
, BorderLayout
.CENTER
);
75 titleLabel
= new JLabel();
76 titleLabel
.setHorizontalAlignment(JLabel
.CENTER
);
77 add(titleLabel
, BorderLayout
.NORTH
);
80 // Compute the number of items per line at each resize
81 addComponentListener(new ComponentAdapter() {
83 public void componentResized(ComponentEvent e
) {
84 super.componentResized(e
);
85 computeItemsPerLine();
88 computeItemsPerLine();
90 addKeyListener(new KeyAdapter() {
92 public void keyPressed(KeyEvent e
) {
97 public void keyTyped(KeyEvent e
) {
102 addFocusListener(new FocusAdapter() {
104 public void focusGained(FocusEvent e
) {
105 if (getSelectedBookIndex() < 0) {
106 setSelectedBook(0, true);
111 public void focusLost(FocusEvent e
) {
113 setSelectedBook(-1, false);
119 * Note: this class supports NULL as a background colour, which will revert
120 * it to its default state.
122 * Note: this class' implementation will also set the main pane background
123 * colour at the same time.
125 * Sets the background colour of this component. The background colour is
126 * used only if the component is opaque, and only by subclasses of
127 * <code>JComponent</code> or <code>ComponentUI</code> implementations.
128 * Direct subclasses of <code>JComponent</code> must override
129 * <code>paintComponent</code> to honour this property.
131 * It is up to the look and feel to honour this property, some may choose to
134 * @param backgroundColor
135 * the desired background <code>Colour</code>
136 * @see java.awt.Component#getBackground
139 * @beaninfo preferred: true bound: true attribute: visualUpdate true
140 * description: The background colour of the component.
143 public void setBackground(Color backgroundColor
) {
144 this.backgroundColor
= backgroundColor
;
146 Color cme
= backgroundColor
== null ? backgroundColorDef
148 Color cpane
= backgroundColor
== null ? backgroundColorDefPane
151 if (pane
!= null) { // can happen at theme setup time
152 pane
.setBackground(cpane
);
154 super.setBackground(cme
);
158 * The title of this group (can be NULL for "no title", an empty
159 * {@link String} will trigger a default title for empty groups)
164 public void setTitle(String title
) {
166 if (title
.isEmpty()) {
167 title
= GuiReader
.trans(StringIdGui
.MENU_AUTHORS_UNKNOWN
);
170 titleLabel
.setText(String
.format("<html>"
171 + "<body style='text-align: center; color: gray;'><br><b>"
172 + "%s" + "</b></body>" + "</html>", title
));
173 titleLabel
.setVisible(true);
175 titleLabel
.setVisible(false);
180 * Compute how many items can fit in a line so UP and DOWN can be used to go
181 * up/down one line at a time.
183 private void computeItemsPerLine() {
186 if (books
!= null && books
.size() > 0) {
187 // this.pane holds all the books with a hgap of 5 px
188 int wbook
= books
.get(0).getWidth() + 5;
189 itemsPerLine
= pane
.getWidth() / wbook
;
194 * Set the {@link ActionListener} that will be fired on each
195 * {@link GuiReaderBook} action.
200 public void setActionListener(BookActionListener action
) {
201 this.action
= action
;
206 * Clear all the books in this {@link GuiReaderGroup}.
208 public void clear() {
209 refreshBooks(new ArrayList
<GuiReaderBookInfo
>());
213 * Refresh the list of {@link GuiReaderBook}s displayed in the control.
215 public void refreshBooks() {
216 refreshBooks(infos
, words
);
220 * Refresh the list of {@link GuiReaderBook}s displayed in the control.
223 * the new list of infos
225 public void refreshBooks(List
<GuiReaderBookInfo
> infos
) {
226 refreshBooks(infos
, words
);
230 * Refresh the list of {@link GuiReaderBook}s displayed in the control.
233 * the new list of infos
234 * @param seeWordcount
235 * TRUE to see word counts, FALSE to see authors
237 public void refreshBooks(List
<GuiReaderBookInfo
> infos
, boolean seeWordcount
) {
239 refreshBooks(seeWordcount
);
243 * Refresh the list of {@link GuiReaderBook}s displayed in the control.
245 * Will not change the current stories.
247 * @param seeWordcount
248 * TRUE to see word counts, FALSE to see authors
250 public void refreshBooks(boolean seeWordcount
) {
251 this.words
= seeWordcount
;
253 books
= new ArrayList
<GuiReaderBook
>();
259 for (GuiReaderBookInfo info
: infos
) {
260 boolean isCached
= false;
261 if (info
.getMeta() != null && info
.getMeta().getLuid() != null) {
262 isCached
= reader
.isCached(info
.getMeta().getLuid());
265 GuiReaderBook book
= new GuiReaderBook(reader
, info
, isCached
,
267 if (backgroundColor
!= null) {
268 book
.setBackground(backgroundColor
);
273 book
.addActionListener(new BookActionListener() {
275 public void select(GuiReaderBook book
) {
276 GuiReaderGroup
.this.requestFocusInWindow();
277 for (GuiReaderBook abook
: books
) {
278 abook
.setSelected(abook
== book
);
283 public void popupRequested(GuiReaderBook book
,
284 Component target
, int x
, int y
) {
288 public void action(GuiReaderBook book
) {
292 if (action
!= null) {
293 book
.addActionListener(action
);
305 computeItemsPerLine();
309 * Enables or disables this component, depending on the value of the
310 * parameter <code>b</code>. An enabled component can respond to user input
311 * and generate events. Components are enabled initially by default.
313 * Disabling this component will also affect its children.
316 * If <code>true</code>, this component is enabled; otherwise
317 * this component is disabled
320 public void setEnabled(boolean b
) {
322 for (GuiReaderBook book
: books
) {
334 * The number of books in this group.
338 public int getBooksCount() {
343 * Return the index of the currently selected book if any, -1 if none.
345 * @return the index or -1
347 public int getSelectedBookIndex() {
349 for (int i
= 0; i
< books
.size(); i
++) {
350 if (books
.get(i
).isSelected()) {
359 * Select the given book, or unselect all items.
362 * the index of the book to select, can be outside the bounds
363 * (either all the items will be unselected or the first or last
364 * book will then be selected, see <tt>forceRange></tt>)
366 * TRUE to constraint the index to the first/last element, FALSE
367 * to unselect when outside the range
369 public void setSelectedBook(int index
, boolean forceRange
) {
370 int previousIndex
= getSelectedBookIndex();
372 if (index
>= books
.size()) {
374 index
= books
.size() - 1;
380 if (index
< 0 && forceRange
) {
384 if (previousIndex
>= 0) {
385 books
.get(previousIndex
).setSelected(false);
388 if (index
>= 0 && !books
.isEmpty()) {
389 books
.get(index
).setSelected(true);
394 * The action to execute when a key is typed.
399 private void onKeyTyped(KeyEvent e
) {
400 boolean consumed
= false;
401 boolean action
= e
.getKeyChar() == '\n';
402 boolean popup
= e
.getKeyChar() == ' ';
403 if (action
|| popup
) {
406 int index
= getSelectedBookIndex();
408 GuiReaderBook book
= books
.get(index
);
412 book
.popup(book
, book
.getWidth() / 2, book
.getHeight() / 2);
423 * The action to execute when a key is pressed.
428 private void onKeyPressed(KeyEvent e
) {
429 boolean consumed
= false;
430 if (e
.isActionKey()) {
432 switch (e
.getKeyCode()) {
433 case KeyEvent
.VK_LEFT
:
436 case KeyEvent
.VK_RIGHT
:
440 offset
= -itemsPerLine
;
442 case KeyEvent
.VK_DOWN
:
443 offset
= itemsPerLine
;
450 int previousIndex
= getSelectedBookIndex();
451 if (previousIndex
>= 0) {
452 setSelectedBook(previousIndex
+ offset
, true);
463 public void paint(Graphics g
) {
466 Rectangle clip
= g
.getClipBounds();
467 if (clip
.getWidth() <= 0 || clip
.getHeight() <= 0) {
472 g
.setColor(new Color(128, 128, 128, 128));
473 g
.fillRect(clip
.x
, clip
.y
, clip
.width
, clip
.height
);