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