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