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