Merge branch 'subtree'
[nikiroo-utils.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderBook.java
1 package be.nikiroo.fanfix.reader.ui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.Graphics;
6 import java.awt.event.MouseEvent;
7 import java.awt.event.MouseListener;
8 import java.util.ArrayList;
9 import java.util.Date;
10 import java.util.EventListener;
11 import java.util.List;
12
13 import javax.swing.JLabel;
14 import javax.swing.JPanel;
15
16 import be.nikiroo.fanfix.data.Story;
17 import be.nikiroo.fanfix.reader.Reader;
18
19 /**
20 * A book item presented in a {@link GuiReaderFrame}.
21 * <p>
22 * Can be a story, or a comic or... a group.
23 *
24 * @author niki
25 */
26 class GuiReaderBook extends JPanel {
27 /**
28 * Action on a book item.
29 *
30 * @author niki
31 */
32 interface BookActionListener extends EventListener {
33 /**
34 * The book was selected (single click).
35 *
36 * @param book
37 * the {@link GuiReaderBook} itself
38 */
39 public void select(GuiReaderBook book);
40
41 /**
42 * The book was double-clicked.
43 *
44 * @param book
45 * the {@link GuiReaderBook} itself
46 */
47 public void action(GuiReaderBook book);
48
49 /**
50 * A popup menu was requested for this {@link GuiReaderBook}.
51 *
52 * @param book
53 * the {@link GuiReaderBook} itself
54 * @param target
55 * the target component for the popup
56 * @param x
57 * the X position of the click/request (in case of popup
58 * request from the keyboard, the center of the target is
59 * selected as point of reference)
60 * @param y
61 * the Y position of the click/request (in case of popup
62 * request from the keyboard, the center of the target is
63 * selected as point of reference)
64 */
65 public void popupRequested(GuiReaderBook book, Component target, int x,
66 int y);
67 }
68
69 private static final long serialVersionUID = 1L;
70
71 private static final String AUTHOR_COLOR = "#888888";
72 private static final long doubleClickDelay = 200; // in ms
73
74 private JLabel icon;
75 private JLabel title;
76 private boolean selected;
77 private boolean hovered;
78 private Date lastClick;
79
80 private List<BookActionListener> listeners;
81 private GuiReaderBookInfo info;
82 private boolean cached;
83 private boolean seeWordCount;
84
85 /**
86 * Create a new {@link GuiReaderBook} item for the given {@link Story}.
87 *
88 * @param reader
89 * the associated reader
90 * @param info
91 * the information about the story to represent
92 * @param cached
93 * TRUE if it is locally cached
94 * @param seeWordCount
95 * TRUE to see word counts, FALSE to see authors
96 */
97 public GuiReaderBook(Reader reader, GuiReaderBookInfo info, boolean cached,
98 boolean seeWordCount) {
99 this.info = info;
100 this.cached = cached;
101 this.seeWordCount = seeWordCount;
102
103 icon = new JLabel(GuiReaderCoverImager.generateCoverIcon(
104 reader.getLibrary(), info));
105
106 title = new JLabel();
107 updateTitle();
108
109 setLayout(new BorderLayout(10, 10));
110 add(icon, BorderLayout.CENTER);
111 add(title, BorderLayout.SOUTH);
112
113 setupListeners();
114 }
115
116 /**
117 * The book current selection state.
118 *
119 * @return the selection state
120 */
121 public boolean isSelected() {
122 return selected;
123 }
124
125 /**
126 * The book current selection state.
127 * <p>
128 * Setting this value to true can cause a "select" action to occur if the
129 * previous state was "unselected".
130 *
131 * @param selected
132 * TRUE if it is selected
133 */
134 public void setSelected(boolean selected) {
135 if (this.selected != selected) {
136 this.selected = selected;
137 repaint();
138
139 if (selected) {
140 select();
141 }
142 }
143 }
144
145 /**
146 * The item mouse-hover state.
147 *
148 * @return TRUE if it is mouse-hovered
149 */
150 public boolean isHovered() {
151 return this.hovered;
152 }
153
154 /**
155 * The item mouse-hover state.
156 *
157 * @param hovered
158 * TRUE if it is mouse-hovered
159 */
160 public void setHovered(boolean hovered) {
161 if (this.hovered != hovered) {
162 this.hovered = hovered;
163 repaint();
164 }
165 }
166
167 /**
168 * Setup the mouse listener that will activate {@link BookActionListener}
169 * events.
170 */
171 private void setupListeners() {
172 listeners = new ArrayList<GuiReaderBook.BookActionListener>();
173 addMouseListener(new MouseListener() {
174 @Override
175 public void mouseReleased(MouseEvent e) {
176 if (isEnabled() && e.isPopupTrigger()) {
177 popup(e);
178 }
179 }
180
181 @Override
182 public void mousePressed(MouseEvent e) {
183 if (isEnabled() && e.isPopupTrigger()) {
184 popup(e);
185 }
186 }
187
188 @Override
189 public void mouseExited(MouseEvent e) {
190 setHovered(false);
191 }
192
193 @Override
194 public void mouseEntered(MouseEvent e) {
195 setHovered(true);
196 }
197
198 @Override
199 public void mouseClicked(MouseEvent e) {
200 if (isEnabled()) {
201 Date now = new Date();
202 if (lastClick != null
203 && now.getTime() - lastClick.getTime() < doubleClickDelay) {
204 click(true);
205 } else {
206 click(false);
207 }
208
209 lastClick = now;
210 e.consume();
211 }
212 }
213
214 private void click(boolean doubleClick) {
215 if (doubleClick) {
216 action();
217 } else {
218 select();
219 }
220 }
221
222 private void popup(MouseEvent e) {
223 GuiReaderBook.this
224 .popup(GuiReaderBook.this, e.getX(), e.getY());
225 e.consume();
226 }
227 });
228 }
229
230 /**
231 * Add a new {@link BookActionListener} on this item.
232 *
233 * @param listener
234 * the listener
235 */
236 public void addActionListener(BookActionListener listener) {
237 listeners.add(listener);
238 }
239
240 /**
241 * Cause an action to occur on this {@link GuiReaderBook}.
242 */
243 public void action() {
244 for (BookActionListener listener : listeners) {
245 listener.action(GuiReaderBook.this);
246 }
247 }
248
249 /**
250 * Cause a select event on this {@link GuiReaderBook}.
251 * <p>
252 * Have a look at {@link GuiReaderBook#setSelected(boolean)}.
253 */
254 private void select() {
255 for (BookActionListener listener : listeners) {
256 listener.select(GuiReaderBook.this);
257 }
258 }
259
260 /**
261 * Request a popup.
262 *
263 * @param target
264 * the target component for the popup
265 * @param x
266 * the X position of the click/request (in case of popup request
267 * from the keyboard, the center of the target should be selected
268 * as point of reference)
269 * @param y
270 * the Y position of the click/request (in case of popup request
271 * from the keyboard, the center of the target should be selected
272 * as point of reference)
273 */
274 public void popup(Component target, int x, int y) {
275 for (BookActionListener listener : listeners) {
276 listener.select((GuiReaderBook.this));
277 listener.popupRequested(GuiReaderBook.this, target, x, y);
278 }
279 }
280
281 /**
282 * The information about the book represented by this item.
283 *
284 * @return the meta
285 */
286 public GuiReaderBookInfo getInfo() {
287 return info;
288 }
289
290 /**
291 * This item {@link GuiReader} library cache state.
292 *
293 * @return TRUE if it is present in the {@link GuiReader} cache
294 */
295 public boolean isCached() {
296 return cached;
297 }
298
299 /**
300 * This item {@link GuiReader} library cache state.
301 *
302 * @param cached
303 * TRUE if it is present in the {@link GuiReader} cache
304 */
305 public void setCached(boolean cached) {
306 if (this.cached != cached) {
307 this.cached = cached;
308 repaint();
309 }
310 }
311
312 /**
313 * Update the title, paint the item, then call
314 * {@link GuiReaderCoverImager#paintOverlay(Graphics, boolean, boolean, boolean, boolean)}
315 * .
316 */
317 @Override
318 public void paint(Graphics g) {
319 updateTitle();
320 super.paint(g);
321 GuiReaderCoverImager.paintOverlay(g, isEnabled(), isSelected(),
322 isHovered(), isCached());
323 }
324
325 /**
326 * Update the title with the currently registered information.
327 */
328 private void updateTitle() {
329 String optSecondary = info.getSecondaryInfo(seeWordCount);
330 title.setText(String
331 .format("<html>"
332 + "<body style='width: %d px; height: %d px; text-align: center'>"
333 + "%s" + "<br>" + "<span style='color: %s;'>" + "%s"
334 + "</span>" + "</body>" + "</html>",
335 GuiReaderCoverImager.TEXT_WIDTH,
336 GuiReaderCoverImager.TEXT_HEIGHT, info.getMainInfo(),
337 AUTHOR_COLOR, optSecondary));
338 }
339 }