Version 1.2.1: fixes for GUI, new buttons
[fanfix.git] / src / be / nikiroo / fanfix / reader / LocalReaderBook.java
1 package be.nikiroo.fanfix.reader;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Graphics;
6 import java.awt.Graphics2D;
7 import java.awt.Polygon;
8 import java.awt.Rectangle;
9 import java.awt.event.MouseEvent;
10 import java.awt.event.MouseListener;
11 import java.awt.image.BufferedImage;
12 import java.util.ArrayList;
13 import java.util.Date;
14 import java.util.EventListener;
15 import java.util.List;
16
17 import javax.swing.ImageIcon;
18 import javax.swing.JLabel;
19 import javax.swing.JPanel;
20
21 import be.nikiroo.fanfix.data.MetaData;
22
23 /**
24 * A book item presented in a {@link LocalReaderFrame}.
25 *
26 * @author niki
27 */
28 class LocalReaderBook extends JPanel {
29 /**
30 * Action on a book item.
31 *
32 * @author niki
33 */
34 interface BookActionListener extends EventListener {
35 /**
36 * The book was selected (single click).
37 *
38 * @param book
39 * the {@link LocalReaderBook} itself
40 */
41 public void select(LocalReaderBook book);
42
43 /**
44 * The book was double-clicked.
45 *
46 * @param book
47 * the {@link LocalReaderBook} itself
48 */
49 public void action(LocalReaderBook book);
50
51 /**
52 * A popup menu was requested for this {@link LocalReaderBook}.
53 *
54 * @param book
55 * the {@link LocalReaderBook} itself
56 * @param e
57 * the {@link MouseEvent} that generated this call
58 */
59 public void popupRequested(LocalReaderBook book, MouseEvent e);
60 }
61
62 private static final int COVER_WIDTH = 100;
63 private static final int COVER_HEIGHT = 150;
64 private static final int SPINE_WIDTH = 5;
65 private static final int SPINE_HEIGHT = 5;
66 private static final int HOFFSET = 20;
67 private static final Color SPINE_COLOR_BOTTOM = new Color(180, 180, 180);
68 private static final Color SPINE_COLOR_RIGHT = new Color(100, 100, 100);
69 private static final int TEXT_WIDTH = COVER_WIDTH + 40;
70 private static final int TEXT_HEIGHT = 50;
71 private static final String AUTHOR_COLOR = "#888888";
72 private static final long serialVersionUID = 1L;
73
74 private JLabel icon;
75 private JLabel title;
76 private boolean selected;
77 private boolean hovered;
78 private Date lastClick;
79 private long doubleClickDelay = 200; // in ms
80 private List<BookActionListener> listeners;
81 private String luid;
82 private boolean cached;
83
84 public LocalReaderBook(MetaData meta, boolean cached) {
85 this.luid = meta.getLuid();
86 this.cached = cached;
87
88 if (meta.getCover() != null) {
89 BufferedImage resizedImage = new BufferedImage(SPINE_WIDTH
90 + COVER_WIDTH, SPINE_HEIGHT + COVER_HEIGHT + HOFFSET,
91 BufferedImage.TYPE_4BYTE_ABGR);
92 Graphics2D g = resizedImage.createGraphics();
93 g.setColor(Color.white);
94 g.fillRect(0, HOFFSET, COVER_WIDTH, COVER_HEIGHT);
95 g.drawImage(meta.getCover(), 0, HOFFSET, COVER_WIDTH, COVER_HEIGHT,
96 null);
97 g.dispose();
98
99 icon = new JLabel(new ImageIcon(resizedImage));
100 } else {
101 // TODO: a big black "X" ?
102 icon = new JLabel(" [ no cover ] ");
103 }
104
105 String optAuthor = meta.getAuthor();
106 if (optAuthor != null && !optAuthor.isEmpty()) {
107 optAuthor = "(" + optAuthor + ")";
108 }
109 title = new JLabel(
110 String.format(
111 "<html>"
112 + "<body style='width: %d px; height: %d px; text-align: center'>"
113 + "%s" + "<br>" + "<span style='color: %s;'>"
114 + "%s" + "</span>" + "</body>" + "</html>",
115 TEXT_WIDTH, TEXT_HEIGHT, meta.getTitle(), AUTHOR_COLOR,
116 optAuthor));
117
118 this.setLayout(new BorderLayout(10, 10));
119 this.add(icon, BorderLayout.CENTER);
120 this.add(title, BorderLayout.SOUTH);
121
122 setupListeners();
123 setSelected(false);
124 }
125
126 /**
127 * The book current selection state.
128 *
129 * @return the selected
130 */
131 public boolean isSelected() {
132 return selected;
133 }
134
135 /**
136 * The book current selection state.
137 *
138 * @param selected
139 * the selected to set
140 */
141 public void setSelected(boolean selected) {
142 this.selected = selected;
143 repaint();
144 }
145
146 private void setHovered(boolean hovered) {
147 this.hovered = hovered;
148 repaint();
149 }
150
151 private void setupListeners() {
152 listeners = new ArrayList<LocalReaderBook.BookActionListener>();
153 addMouseListener(new MouseListener() {
154 public void mouseReleased(MouseEvent e) {
155 if (e.isPopupTrigger()) {
156 popup(e);
157 }
158 }
159
160 public void mousePressed(MouseEvent e) {
161 if (e.isPopupTrigger()) {
162 popup(e);
163 }
164 }
165
166 public void mouseExited(MouseEvent e) {
167 setHovered(false);
168 }
169
170 public void mouseEntered(MouseEvent e) {
171 setHovered(true);
172 }
173
174 public void mouseClicked(MouseEvent e) {
175 if (isEnabled()) {
176 Date now = new Date();
177 if (lastClick != null
178 && now.getTime() - lastClick.getTime() < doubleClickDelay) {
179 click(true);
180 } else {
181 click(false);
182 }
183
184 lastClick = now;
185 }
186 }
187
188 private void click(boolean doubleClick) {
189 for (BookActionListener listener : listeners) {
190 if (doubleClick) {
191 listener.action(LocalReaderBook.this);
192 } else {
193 listener.select(LocalReaderBook.this);
194 }
195 }
196 }
197
198 private void popup(MouseEvent e) {
199 for (BookActionListener listener : listeners) {
200 listener.select((LocalReaderBook.this));
201 listener.popupRequested(LocalReaderBook.this, e);
202 }
203 }
204 });
205 }
206
207 public void addActionListener(BookActionListener listener) {
208 listeners.add(listener);
209 }
210
211 public String getLuid() {
212 return luid;
213 }
214
215 /**
216 * This boos is cached into the {@link LocalReader} library.
217 *
218 * @return the cached
219 */
220 public boolean isCached() {
221 return cached;
222 }
223
224 /**
225 * This boos is cached into the {@link LocalReader} library.
226 *
227 * @param cached
228 * the cached to set
229 */
230 public void setCached(boolean cached) {
231 this.cached = cached;
232 }
233
234 @Override
235 public void paint(Graphics g) {
236 super.paint(g);
237
238 int h = COVER_HEIGHT;
239 int w = COVER_WIDTH;
240 int xOffset = (TEXT_WIDTH - COVER_WIDTH) - 4;
241
242 int[] xs = new int[] { xOffset, xOffset + SPINE_WIDTH,
243 xOffset + w + SPINE_WIDTH, xOffset + w };
244 int[] ys = new int[] { HOFFSET + h, HOFFSET + h + SPINE_HEIGHT,
245 HOFFSET + h + SPINE_HEIGHT, HOFFSET + h };
246 g.setColor(SPINE_COLOR_BOTTOM);
247 g.fillPolygon(new Polygon(xs, ys, xs.length));
248 xs = new int[] { xOffset + w, xOffset + w + SPINE_WIDTH,
249 xOffset + w + SPINE_WIDTH, xOffset + w };
250 ys = new int[] { HOFFSET, HOFFSET + SPINE_HEIGHT,
251 HOFFSET + h + SPINE_HEIGHT, HOFFSET + h };
252 g.setColor(SPINE_COLOR_RIGHT);
253 g.fillPolygon(new Polygon(xs, ys, xs.length));
254
255 Color color = new Color(255, 255, 255, 0);
256 if (!isEnabled()) {
257 } else if (selected && !hovered) {
258 color = new Color(80, 80, 100, 40);
259 } else if (!selected && hovered) {
260 color = new Color(230, 230, 255, 100);
261 } else if (selected && hovered) {
262 color = new Color(200, 200, 255, 100);
263 }
264
265 Rectangle clip = g.getClipBounds();
266 if (cached) {
267 g.setColor(Color.green);
268 g.fillOval(clip.x + clip.width - 30, 10, 20, 20);
269 }
270
271 g.setColor(color);
272 g.fillRect(clip.x, clip.y, clip.width, clip.height);
273 }
274 }