12d6be6b555b263f8662cf8082a3227e4d3be9ec
[fanfix.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderViewerPanel.java
1 package be.nikiroo.fanfix.reader.ui;
2
3 import java.awt.BorderLayout;
4 import java.awt.EventQueue;
5 import java.awt.Graphics2D;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.awt.image.BufferedImage;
9
10 import javax.swing.Icon;
11 import javax.swing.ImageIcon;
12 import javax.swing.JButton;
13 import javax.swing.JEditorPane;
14 import javax.swing.JLabel;
15 import javax.swing.JPanel;
16 import javax.swing.JProgressBar;
17 import javax.swing.JScrollPane;
18 import javax.swing.SwingConstants;
19
20 import be.nikiroo.fanfix.Instance;
21 import be.nikiroo.fanfix.data.Chapter;
22 import be.nikiroo.fanfix.data.Story;
23 import be.nikiroo.utils.Image;
24 import be.nikiroo.utils.ui.ImageUtilsAwt;
25
26 /**
27 * A {@link JPanel} that will show a {@link Story} chapter on screen.
28 *
29 * @author niki
30 */
31 public class GuiReaderViewerPanel extends JPanel {
32 private static final long serialVersionUID = 1L;
33
34 private boolean imageDocument;
35 private Chapter chap;
36 private JScrollPane scroll;
37 private GuiReaderViewerTextOutput htmlOutput;
38
39 // text only:
40 private JEditorPane text;
41
42 // image only:
43 private JLabel image;
44 private JProgressBar imageProgress;
45 private int currentImage;
46 private JButton left;
47 private JButton right;
48
49 /**
50 * Create a new viewer.
51 *
52 * @param story
53 * the {@link Story} to work on.
54 */
55 public GuiReaderViewerPanel(Story story) {
56 super(new BorderLayout());
57
58 this.imageDocument = story.getMeta().isImageDocument();
59
60 this.text = new JEditorPane("text/html", "");
61 text.setAlignmentY(TOP_ALIGNMENT);
62 htmlOutput = new GuiReaderViewerTextOutput();
63
64 image = new JLabel();
65 image.setHorizontalAlignment(SwingConstants.CENTER);
66
67 scroll = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
68 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
69 scroll.getVerticalScrollBar().setUnitIncrement(16);
70
71 if (!imageDocument) {
72 add(scroll, BorderLayout.CENTER);
73 } else {
74 imageProgress = new JProgressBar();
75 imageProgress.setStringPainted(true);
76 add(imageProgress, BorderLayout.SOUTH);
77
78 JPanel main = new JPanel(new BorderLayout());
79 main.add(scroll, BorderLayout.CENTER);
80
81 left = new JButton("<HTML>&nbsp; &nbsp; &lt; &nbsp; &nbsp;</HTML>");
82 left.addActionListener(new ActionListener() {
83 @Override
84 public void actionPerformed(ActionEvent e) {
85 setImage(--currentImage);
86 }
87 });
88 main.add(left, BorderLayout.WEST);
89
90 right = new JButton("<HTML>&nbsp; &nbsp; &gt; &nbsp; &nbsp;</HTML>");
91 right.addActionListener(new ActionListener() {
92 @Override
93 public void actionPerformed(ActionEvent e) {
94 setImage(++currentImage);
95 }
96 });
97 main.add(right, BorderLayout.EAST);
98
99 add(main, BorderLayout.CENTER);
100 main.invalidate();
101 }
102
103 setChapter(story.getMeta().getResume());
104 }
105
106 /**
107 * Load the given chapter.
108 * <p>
109 * Will always be text for a non-image document.
110 * <p>
111 * Will be an image and left/right controls for an image-document, except
112 * for chapter 0 which will be text (chapter 0 = resume).
113 *
114 * @param chap
115 * the chapter to load
116 */
117 public void setChapter(Chapter chap) {
118 this.chap = chap;
119
120 if (!imageDocument) {
121 setText(chap);
122 } else {
123 left.setVisible(chap.getNumber() > 0);
124 right.setVisible(chap.getNumber() > 0);
125 imageProgress.setVisible(chap.getNumber() > 0);
126
127 imageProgress.setMinimum(0);
128 imageProgress.setMaximum(chap.getParagraphs().size() - 1);
129
130 if (chap.getNumber() == 0) {
131 setText(chap);
132 } else {
133 setImage(0);
134 }
135 }
136 }
137
138 /**
139 * Will set and display the current chapter text.
140 *
141 * @param chap
142 * the chapter to display
143 */
144 private void setText(final Chapter chap) {
145 new Thread(new Runnable() {
146 @Override
147 public void run() {
148 final String content = htmlOutput.convert(chap);
149 // Wait until size computations are correct
150 while (!scroll.isValid()) {
151 try {
152 Thread.sleep(1);
153 } catch (InterruptedException e) {
154 }
155 }
156
157 setText(content);
158 }
159 }).start();
160 }
161
162 /**
163 * Actually set the text in the UI.
164 * <p>
165 * Do <b>NOT</b> use this method from the UI thread.
166 *
167 * @param content
168 * the text
169 */
170 private void setText(final String content) {
171 EventQueue.invokeLater(new Runnable() {
172 @Override
173 public void run() {
174 text.setText(content);
175 text.setCaretPosition(0);
176 scroll.setViewportView(text);
177 }
178 });
179 }
180
181 /**
182 * Will set and display the current image, take care about the progression
183 * and update the left and right cursors' <tt>enabled</tt> property.
184 *
185 * @param i
186 * the image index to load
187 */
188 private void setImage(int i) {
189 left.setEnabled(i > 0);
190 right.setEnabled(i + 1 < chap.getParagraphs().size());
191
192 if (i < 0 || i >= chap.getParagraphs().size()) {
193 return;
194 }
195
196 imageProgress.setValue(i);
197 imageProgress.setString(String.format("Image %d / %d", i + 1, chap
198 .getParagraphs().size()));
199
200 currentImage = i;
201
202 final Image img = chap.getParagraphs().get(i).getContentImage();
203
204 // prepare the viewport to get the right sizes later on
205 image.setIcon(null);
206 scroll.setViewportView(image);
207
208 new Thread(new Runnable() {
209 @Override
210 public void run() {
211 // Wait until size computations are correct
212 while (!scroll.isValid()) {
213 try {
214 Thread.sleep(1);
215 } catch (InterruptedException e) {
216 }
217 }
218
219 if (img == null) {
220 setText("Error: cannot render image.");
221 } else {
222 setImage(img);
223 }
224 }
225 }).start();
226 }
227
228 /**
229 * Actually set the image in the UI.
230 * <p>
231 * Do <b>NOT</b> use this method from the UI thread.
232 *
233 * @param img
234 * the image to set
235 */
236 private void setImage(Image img) {
237 try {
238 int scrollWidth = scroll.getWidth()
239 - scroll.getVerticalScrollBar().getWidth();
240
241 BufferedImage buffImg = ImageUtilsAwt.fromImage(img);
242
243 int iw = buffImg.getWidth();
244 int ih = buffImg.getHeight();
245 double ratio = ((double) ih) / iw;
246
247 int w = scrollWidth;
248 int h = (int) (ratio * scrollWidth);
249
250 BufferedImage resizedImage = new BufferedImage(w, h,
251 BufferedImage.TYPE_4BYTE_ABGR);
252
253 Graphics2D g = resizedImage.createGraphics();
254 try {
255 g.drawImage(buffImg, 0, 0, w, h, null);
256 } finally {
257 g.dispose();
258 }
259
260 final Icon icon = new ImageIcon(resizedImage);
261 EventQueue.invokeLater(new Runnable() {
262 @Override
263 public void run() {
264 image.setIcon(icon);
265 scroll.setViewportView(image);
266 }
267 });
268 } catch (Exception e) {
269 Instance.getTraceHandler().error(
270 new Exception("Failed to load image into label", e));
271 EventQueue.invokeLater(new Runnable() {
272 @Override
273 public void run() {
274 text.setText("Error: cannot load image.");
275 }
276 });
277 }
278 }
279 }